@jupitermetalabs/face-zk-sdk 0.3.4 → 0.3.7

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 (74) hide show
  1. package/README.md +46 -1
  2. package/assets/face-guidance/pose-guidance.js.txt +4 -2
  3. package/assets/liveness/liveness.js.txt +3 -0
  4. package/dist/FaceZkSdk.d.ts +69 -0
  5. package/dist/FaceZkSdk.js +136 -0
  6. package/dist/assets/face-guidance/pose-guidance.js.txt +4 -2
  7. package/dist/assets/liveness/liveness.js.txt +3 -0
  8. package/dist/assets/onnx/ort-min.d.ts +1 -0
  9. package/dist/assets/onnx/ort-min.js +11 -0
  10. package/dist/config/defaults.d.ts +51 -0
  11. package/dist/config/defaults.js +61 -0
  12. package/dist/config/types.d.ts +169 -0
  13. package/dist/config/types.js +17 -0
  14. package/dist/core/enrollment-core.d.ts +70 -0
  15. package/dist/core/enrollment-core.js +206 -0
  16. package/dist/core/idGenerator.d.ts +11 -0
  17. package/dist/core/idGenerator.js +32 -0
  18. package/dist/core/matching.d.ts +69 -0
  19. package/dist/core/matching.js +101 -0
  20. package/dist/core/types.d.ts +379 -0
  21. package/dist/core/types.js +37 -0
  22. package/dist/core/verification-core.d.ts +120 -0
  23. package/dist/core/verification-core.js +442 -0
  24. package/dist/core/zk-core.d.ts +69 -0
  25. package/dist/core/zk-core.js +244 -0
  26. package/dist/index.d.ts +29 -0
  27. package/dist/index.js +51 -0
  28. package/dist/react-native/adapters/faceEmbeddingProvider.d.ts +38 -0
  29. package/dist/react-native/adapters/faceEmbeddingProvider.js +45 -0
  30. package/dist/react-native/adapters/imageDataProvider.d.ts +53 -0
  31. package/dist/react-native/adapters/imageDataProvider.js +134 -0
  32. package/dist/react-native/adapters/livenessProvider.d.ts +133 -0
  33. package/dist/react-native/adapters/livenessProvider.js +150 -0
  34. package/dist/react-native/adapters/zkProofEngine-webview.d.ts +73 -0
  35. package/dist/react-native/adapters/zkProofEngine-webview.js +135 -0
  36. package/dist/react-native/bundledRuntimeAssets.d.ts +39 -0
  37. package/dist/react-native/bundledRuntimeAssets.js +44 -0
  38. package/dist/react-native/components/FacePoseGuidanceWebView.d.ts +30 -0
  39. package/dist/react-native/components/FacePoseGuidanceWebView.js +530 -0
  40. package/dist/react-native/components/LivenessWebView.d.ts +39 -0
  41. package/dist/react-native/components/LivenessWebView.js +386 -0
  42. package/dist/react-native/components/OnnxRuntimeWebView.d.ts +58 -0
  43. package/dist/react-native/components/OnnxRuntimeWebView.js +518 -0
  44. package/dist/react-native/components/ZkProofWebView.d.ts +59 -0
  45. package/dist/react-native/components/ZkProofWebView.js +297 -0
  46. package/dist/react-native/dependencies.d.ts +144 -0
  47. package/dist/react-native/dependencies.js +130 -0
  48. package/dist/react-native/hooks/useOnnxLoader.d.ts +37 -0
  49. package/dist/react-native/hooks/useOnnxLoader.js +74 -0
  50. package/dist/react-native/hooks/useWasmLoader.d.ts +30 -0
  51. package/dist/react-native/hooks/useWasmLoader.js +158 -0
  52. package/dist/react-native/index.d.ts +61 -0
  53. package/dist/react-native/index.js +144 -0
  54. package/dist/react-native/services/FaceRecognition.d.ts +92 -0
  55. package/dist/react-native/services/FaceRecognition.js +674 -0
  56. package/dist/react-native/ui/FaceZkVerificationFlow.d.ts +97 -0
  57. package/dist/react-native/ui/FaceZkVerificationFlow.js +477 -0
  58. package/dist/react-native/ui/ReferenceEnrollmentFlow.d.ts +72 -0
  59. package/dist/react-native/ui/ReferenceEnrollmentFlow.js +369 -0
  60. package/dist/react-native/utils/faceAlignment.d.ts +37 -0
  61. package/dist/react-native/utils/faceAlignment.js +186 -0
  62. package/dist/react-native/utils/modelInitialisationChecks.d.ts +36 -0
  63. package/dist/react-native/utils/modelInitialisationChecks.js +128 -0
  64. package/dist/react-native/utils/resolveModelUri.d.ts +55 -0
  65. package/dist/react-native/utils/resolveModelUri.js +211 -0
  66. package/dist/react-native/utils/resolveRuntimeAsset.d.ts +25 -0
  67. package/dist/react-native/utils/resolveRuntimeAsset.js +94 -0
  68. package/dist/react-native/utils/resolveUiConfig.d.ts +41 -0
  69. package/dist/react-native/utils/resolveUiConfig.js +81 -0
  70. package/dist/storage/defaultStorageAdapter.d.ts +44 -0
  71. package/dist/storage/defaultStorageAdapter.js +344 -0
  72. package/dist/tsconfig.tsbuildinfo +1 -1
  73. package/face-zk.config.example.js +10 -3
  74. package/package.json +2 -2
@@ -0,0 +1,120 @@
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 – Verification Core
18
+ *
19
+ * Headless verification functions for face matching with optional ZK proofs.
20
+ * This module orchestrates reference resolution, live capture, liveness checks,
21
+ * matching, and optional ZK proof generation.
22
+ */
23
+ import type { ReferenceTemplate, ReferenceTemplateInput, ReferenceId, LivenessResult, VerificationOutcome, FaceZkRuntimeConfig, VerificationOptions } from "./types";
24
+ import type { FaceEmbeddingProvider } from "./enrollment-core";
25
+ export type { FaceEmbeddingProvider };
26
+ /**
27
+ * Interface for liveness provider.
28
+ * This will be implemented by platform-specific adapters.
29
+ */
30
+ export interface LivenessProvider {
31
+ /**
32
+ * Run liveness checks on a captured image/frame.
33
+ *
34
+ * @param imageUri URI of the image to check
35
+ * @returns Liveness result with pass/fail and optional checks
36
+ */
37
+ checkLiveness(imageUri: string): Promise<LivenessResult>;
38
+ }
39
+ /**
40
+ * Interface for reading image data (base64, file size, etc.).
41
+ * This keeps the core framework-agnostic - platform adapters provide implementation.
42
+ */
43
+ export interface ImageDataProvider {
44
+ /**
45
+ * Read image as base64 string
46
+ * @param imageUri URI of the image
47
+ * @returns Base64-encoded image string
48
+ */
49
+ readAsBase64(imageUri: string): Promise<string>;
50
+ /**
51
+ * Get image file size in bytes
52
+ * @param imageUri URI of the image
53
+ * @returns Size in bytes
54
+ */
55
+ getFileSizeBytes(imageUri: string): Promise<number>;
56
+ /**
57
+ * Estimate an image quality score (0–1, higher = better).
58
+ * Implementation is platform-specific; a file-size heuristic is acceptable.
59
+ * @param imageUri URI of the image
60
+ * @returns Quality score between 0 and 1
61
+ */
62
+ analyzeQuality?(imageUri: string): Promise<number>;
63
+ }
64
+ /**
65
+ * Per-call options for {@link verifyOnly} and {@link verifyWithProof}.
66
+ * Extends {@link VerificationOptions} with optional provider overrides for this call.
67
+ */
68
+ export interface VerifyCallOptions extends VerificationOptions {
69
+ /** Liveness provider for this call. */
70
+ livenessProvider?: LivenessProvider;
71
+ /** Image-data provider for this call (base64, size, quality). */
72
+ imageDataProvider?: ImageDataProvider;
73
+ }
74
+ /**
75
+ * Performs a face match verification without generating a Zero-Knowledge proof.
76
+ *
77
+ * This function orchestrates the standard verification pipeline:
78
+ * 1. Resolves the enrolled `ReferenceTemplate`.
79
+ * 2. Captures and extracts the live facial embedding via `FaceEmbeddingProvider`.
80
+ * 3. Evaluates anti-spoofing via the optional `LivenessProvider`.
81
+ * 4. Computes the raw L2² distance.
82
+ *
83
+ * **Security Warning:** This bypasses the cryptographic ZK verification. The `success` boolean returned here is based *only* on the liveness result (if enabled). Because the threshold API was removed in v3.0, the SDK does not natively fail the match here; your application logic must interpret the `FaceMatchResult` manually if you choose not to use `verifyWithProof`.
84
+ *
85
+ * @param {ReferenceTemplate | ReferenceTemplateInput | ReferenceId} reference - The enrolled identity to compare against.
86
+ * @param {string} liveImageUri - URI of the live capture frame.
87
+ * @param {FaceZkRuntimeConfig} sdkConfig - Global SDK Configuration.
88
+ * @param {FaceEmbeddingProvider} embeddingProvider - Platform adapter for extracting embeddings.
89
+ * @param {VerifyCallOptions} [options={}] - Per-call options: config overrides plus optional liveness and image-data providers.
90
+ * @returns {Promise<VerificationOutcome>} The completed outcome including the fractional match percentage.
91
+ *
92
+ * @example
93
+ * const outcome = await verifyOnly(refId, 'file:///live.jpg', config, embedProv);
94
+ * @example With providers
95
+ * const outcome = await verifyOnly(refId, uri, config, embedProv, { livenessProvider, liveness: { enabled: true } });
96
+ */
97
+ export declare function verifyOnly(reference: ReferenceTemplate | ReferenceTemplateInput | ReferenceId, liveImageUri: string, sdkConfig: FaceZkRuntimeConfig, embeddingProvider: FaceEmbeddingProvider, options?: VerifyCallOptions): Promise<VerificationOutcome>;
98
+ /**
99
+ * Performs a high-security face match verification backed by a Zero-Knowledge proof.
100
+ *
101
+ * This is the primary verification gateway. It executes the standard pipeline (via `verifyOnly`) and then funnels the resulting exact embeddings into the ZK WASM circuit.
102
+ *
103
+ * **Crypto/ZK Context:** The circuit mathematically guarantees that:
104
+ * 1. The distance between the live and reference embeddings is strictly less than the compiled threshold.
105
+ * 2. The computation was executed faithfully.
106
+ * If this condition is not met, the cryptographic verification fails, and `zkOutcome.success` is forced to `false` (assuming `zk.requiredForSuccess` is true).
107
+ *
108
+ * @param {ReferenceTemplate | ReferenceTemplateInput | ReferenceId} reference - The enrolled identity to verify.
109
+ * @param {string} liveImageUri - URI of the live capture frame.
110
+ * @param {FaceZkRuntimeConfig} sdkConfig - Global SDK configuration requiring `zk.enabled = true`.
111
+ * @param {FaceEmbeddingProvider} embeddingProvider - Platform adapter for extracting embeddings.
112
+ * @param {VerifyCallOptions} [options={}] - Per-call options: config overrides plus optional liveness and image-data providers.
113
+ * @returns {Promise<VerificationOutcome>} The completed outcome, containing the `ZkProofSummary` and cryptographic success state.
114
+ *
115
+ * @example
116
+ * const outcome = await verifyWithProof(refId, uri, config, embedProv);
117
+ * @example With liveness provider
118
+ * const outcome = await verifyWithProof(refId, uri, config, embedProv, { livenessProvider });
119
+ */
120
+ export declare function verifyWithProof(reference: ReferenceTemplate | ReferenceTemplateInput | ReferenceId, liveImageUri: string, sdkConfig: FaceZkRuntimeConfig, embeddingProvider: FaceEmbeddingProvider, options?: VerifyCallOptions): Promise<VerificationOutcome>;
@@ -0,0 +1,442 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright 2026 JupiterMeta Labs
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.verifyOnly = verifyOnly;
19
+ exports.verifyWithProof = verifyWithProof;
20
+ const types_1 = require("./types");
21
+ const matching_1 = require("./matching");
22
+ const idGenerator_1 = require("./idGenerator");
23
+ /**
24
+ * Resolve a reference to a ReferenceTemplate.
25
+ * Accepts:
26
+ * - ReferenceTemplate (pass-through)
27
+ * - ReferenceTemplateInput (convert to ReferenceTemplate)
28
+ * - ReferenceId (load from storage)
29
+ */
30
+ async function resolveReference(reference, sdkConfig) {
31
+ // If it's already a ReferenceTemplate, return as-is
32
+ if (typeof reference === "object" &&
33
+ "referenceId" in reference &&
34
+ "embedding" in reference &&
35
+ "pose" in reference) {
36
+ // Check if it has all required fields of ReferenceTemplate
37
+ if (typeof reference.referenceId === "string" &&
38
+ Array.isArray(reference.embedding) &&
39
+ reference.pose &&
40
+ typeof reference.pose === "object") {
41
+ return reference;
42
+ }
43
+ }
44
+ // If it's a string (ReferenceId), load from storage
45
+ if (typeof reference === "string") {
46
+ const referenceId = reference;
47
+ if (!sdkConfig.storage) {
48
+ const error = {
49
+ code: "NO_REFERENCE",
50
+ message: "Reference ID provided but no storage adapter configured",
51
+ details: { referenceId },
52
+ };
53
+ throw error;
54
+ }
55
+ sdkConfig.onLog?.({
56
+ level: "debug",
57
+ message: "Loading reference from storage",
58
+ context: { referenceId },
59
+ });
60
+ const template = await sdkConfig.storage.loadReference(referenceId);
61
+ if (!template) {
62
+ const error = {
63
+ code: "NO_REFERENCE",
64
+ message: `Reference not found: ${referenceId}`,
65
+ details: { referenceId },
66
+ };
67
+ throw error;
68
+ }
69
+ return template;
70
+ }
71
+ // Otherwise, it's a ReferenceTemplateInput - convert to ReferenceTemplate
72
+ const input = reference;
73
+ // Generate referenceId if not provided
74
+ const referenceId = input.referenceId ||
75
+ generateSecureId("ref");
76
+ return {
77
+ referenceId,
78
+ embedding: input.embedding,
79
+ pose: input.pose,
80
+ metadata: input.metadata,
81
+ };
82
+ }
83
+ /**
84
+ * Compute live capture result from an image URI.
85
+ * Extracts embedding, pose, and optional image metadata.
86
+ */
87
+ async function computeLiveCapture(liveImageUri, embeddingProvider, sdkConfig, options, imageDataProvider) {
88
+ sdkConfig.onLog?.({
89
+ level: "debug",
90
+ message: "Computing live capture",
91
+ context: { liveImageUri },
92
+ });
93
+ const result = await embeddingProvider.processImageForEmbedding(liveImageUri);
94
+ if (result.status !== "ok") {
95
+ const errorCode = result.status === "no_face"
96
+ ? "NO_FACE"
97
+ : result.status === "multiple_faces"
98
+ ? "MULTIPLE_FACES"
99
+ : "SYSTEM_ERROR";
100
+ const error = {
101
+ code: errorCode,
102
+ message: result.message || `Face processing failed: ${result.status}`,
103
+ details: { stage: "live_capture", status: result.status },
104
+ };
105
+ throw error;
106
+ }
107
+ if (!result.embedding) {
108
+ const error = {
109
+ code: "SYSTEM_ERROR",
110
+ message: "Face processing succeeded but missing embedding",
111
+ details: { stage: "live_capture" },
112
+ };
113
+ throw error;
114
+ }
115
+ // Build image info
116
+ const imageInfo = {
117
+ imageUri: liveImageUri,
118
+ };
119
+ // Optionally compute base64, sizeKb, qualityScore if requested
120
+ if (options?.includeImageData && imageDataProvider) {
121
+ const { base64, sizeKb, qualityScore } = options.includeImageData;
122
+ if (base64 || sizeKb) {
123
+ try {
124
+ // Use platform-specific image data provider (keeps core framework-agnostic)
125
+ const imageBase64 = await imageDataProvider.readAsBase64(liveImageUri);
126
+ if (base64) {
127
+ imageInfo.imageBase64 = imageBase64;
128
+ }
129
+ if (sizeKb) {
130
+ // Calculate size from base64 length
131
+ // Base64 is ~33% larger than binary, so we divide by 1.33 and convert to KB
132
+ const binarySize = (imageBase64.length * 3) / 4;
133
+ imageInfo.sizeKb = Math.round((binarySize / 1024) * 100) / 100;
134
+ }
135
+ }
136
+ catch (error) {
137
+ sdkConfig.onLog?.({
138
+ level: "warn",
139
+ message: "Failed to read image data",
140
+ context: { error: String(error) },
141
+ });
142
+ }
143
+ }
144
+ if (qualityScore && imageDataProvider?.analyzeQuality) {
145
+ imageInfo.qualityScore = await imageDataProvider.analyzeQuality(liveImageUri);
146
+ }
147
+ }
148
+ else if (options?.includeImageData && !imageDataProvider) {
149
+ sdkConfig.onLog?.({
150
+ level: "warn",
151
+ message: "includeImageData requested but no ImageDataProvider provided, skipping image data",
152
+ });
153
+ }
154
+ return {
155
+ embedding: result.embedding,
156
+ pose: result.pose,
157
+ image: imageInfo,
158
+ gender: result.gender,
159
+ age: result.age,
160
+ };
161
+ }
162
+ /**
163
+ * Merge verification options with SDK config.
164
+ */
165
+ function mergeConfig(sdkConfig, options) {
166
+ const merged = { ...sdkConfig };
167
+ // Merge liveness config
168
+ if (options.liveness && sdkConfig.liveness) {
169
+ merged.liveness = { ...sdkConfig.liveness, ...options.liveness };
170
+ }
171
+ else if (sdkConfig.liveness) {
172
+ merged.liveness = sdkConfig.liveness;
173
+ }
174
+ // Merge ZK config
175
+ if (options.zk && sdkConfig.zk) {
176
+ merged.zk = { ...sdkConfig.zk, ...options.zk };
177
+ }
178
+ else if (sdkConfig.zk) {
179
+ merged.zk = sdkConfig.zk;
180
+ }
181
+ return merged;
182
+ }
183
+ /**
184
+ * Performs a face match verification without generating a Zero-Knowledge proof.
185
+ *
186
+ * This function orchestrates the standard verification pipeline:
187
+ * 1. Resolves the enrolled `ReferenceTemplate`.
188
+ * 2. Captures and extracts the live facial embedding via `FaceEmbeddingProvider`.
189
+ * 3. Evaluates anti-spoofing via the optional `LivenessProvider`.
190
+ * 4. Computes the raw L2² distance.
191
+ *
192
+ * **Security Warning:** This bypasses the cryptographic ZK verification. The `success` boolean returned here is based *only* on the liveness result (if enabled). Because the threshold API was removed in v3.0, the SDK does not natively fail the match here; your application logic must interpret the `FaceMatchResult` manually if you choose not to use `verifyWithProof`.
193
+ *
194
+ * @param {ReferenceTemplate | ReferenceTemplateInput | ReferenceId} reference - The enrolled identity to compare against.
195
+ * @param {string} liveImageUri - URI of the live capture frame.
196
+ * @param {FaceZkRuntimeConfig} sdkConfig - Global SDK Configuration.
197
+ * @param {FaceEmbeddingProvider} embeddingProvider - Platform adapter for extracting embeddings.
198
+ * @param {VerifyCallOptions} [options={}] - Per-call options: config overrides plus optional liveness and image-data providers.
199
+ * @returns {Promise<VerificationOutcome>} The completed outcome including the fractional match percentage.
200
+ *
201
+ * @example
202
+ * const outcome = await verifyOnly(refId, 'file:///live.jpg', config, embedProv);
203
+ * @example With providers
204
+ * const outcome = await verifyOnly(refId, uri, config, embedProv, { livenessProvider, liveness: { enabled: true } });
205
+ */
206
+ async function verifyOnly(reference, liveImageUri, sdkConfig, embeddingProvider, options = {}) {
207
+ const { livenessProvider, imageDataProvider, ...configOptions } = options;
208
+ const config = mergeConfig(sdkConfig, configOptions);
209
+ config.onLog?.({
210
+ level: "info",
211
+ message: "Starting verification (match only)",
212
+ context: { liveImageUri },
213
+ });
214
+ try {
215
+ // Step 1: Resolve reference
216
+ const resolvedReference = await resolveReference(reference, config);
217
+ // Step 2: Compute live capture
218
+ const liveCapture = await computeLiveCapture(liveImageUri, embeddingProvider, config, options, imageDataProvider);
219
+ // Step 3: Run liveness (if enabled)
220
+ let livenessResult;
221
+ if (config.liveness?.enabled && livenessProvider) {
222
+ config.onLog?.({
223
+ level: "debug",
224
+ message: "Running liveness checks",
225
+ });
226
+ livenessResult = await livenessProvider.checkLiveness(liveImageUri);
227
+ if (!livenessResult.passed) {
228
+ config.onLog?.({
229
+ level: "warn",
230
+ message: "Liveness check failed",
231
+ context: { livenessResult },
232
+ });
233
+ return {
234
+ success: false,
235
+ score: 0,
236
+ liveness: livenessResult,
237
+ reference: resolvedReference,
238
+ live: liveCapture,
239
+ error: {
240
+ code: "LIVENESS_FAILED",
241
+ message: "Liveness check did not pass",
242
+ details: { stage: "liveness", livenessResult },
243
+ },
244
+ };
245
+ }
246
+ }
247
+ // Step 4: Compute match result
248
+ config.onLog?.({
249
+ level: "debug",
250
+ message: "Computing face match",
251
+ });
252
+ const matchResult = (0, matching_1.computeFaceMatchResult)(resolvedReference.embedding, liveCapture.embedding);
253
+ config.onLog?.({
254
+ level: "info",
255
+ message: "Match computation complete",
256
+ context: {
257
+ distance: matchResult.distance,
258
+ matchPercentage: matchResult.matchPercentage,
259
+ },
260
+ });
261
+ // Step 5: Build outcome
262
+ // Pass/fail is determined by the ZK engine; here we only gate on liveness.
263
+ const success = livenessResult?.passed ?? true;
264
+ return {
265
+ success,
266
+ score: matchResult.matchPercentage,
267
+ match: matchResult,
268
+ liveness: livenessResult,
269
+ reference: resolvedReference,
270
+ live: liveCapture,
271
+ };
272
+ }
273
+ catch (error) {
274
+ // If it's already an SdkError, wrap it in outcome
275
+ if ((0, types_1.isSdkError)(error)) {
276
+ return {
277
+ success: false,
278
+ score: 0,
279
+ error,
280
+ };
281
+ }
282
+ // Wrap unexpected errors
283
+ const sdkError = {
284
+ code: "SYSTEM_ERROR",
285
+ message: error instanceof Error ? error.message : "Unknown error",
286
+ details: { stage: "verification", originalError: String(error) },
287
+ };
288
+ return {
289
+ success: false,
290
+ score: 0,
291
+ error: sdkError,
292
+ };
293
+ }
294
+ }
295
+ /**
296
+ * Performs a high-security face match verification backed by a Zero-Knowledge proof.
297
+ *
298
+ * This is the primary verification gateway. It executes the standard pipeline (via `verifyOnly`) and then funnels the resulting exact embeddings into the ZK WASM circuit.
299
+ *
300
+ * **Crypto/ZK Context:** The circuit mathematically guarantees that:
301
+ * 1. The distance between the live and reference embeddings is strictly less than the compiled threshold.
302
+ * 2. The computation was executed faithfully.
303
+ * If this condition is not met, the cryptographic verification fails, and `zkOutcome.success` is forced to `false` (assuming `zk.requiredForSuccess` is true).
304
+ *
305
+ * @param {ReferenceTemplate | ReferenceTemplateInput | ReferenceId} reference - The enrolled identity to verify.
306
+ * @param {string} liveImageUri - URI of the live capture frame.
307
+ * @param {FaceZkRuntimeConfig} sdkConfig - Global SDK configuration requiring `zk.enabled = true`.
308
+ * @param {FaceEmbeddingProvider} embeddingProvider - Platform adapter for extracting embeddings.
309
+ * @param {VerifyCallOptions} [options={}] - Per-call options: config overrides plus optional liveness and image-data providers.
310
+ * @returns {Promise<VerificationOutcome>} The completed outcome, containing the `ZkProofSummary` and cryptographic success state.
311
+ *
312
+ * @example
313
+ * const outcome = await verifyWithProof(refId, uri, config, embedProv);
314
+ * @example With liveness provider
315
+ * const outcome = await verifyWithProof(refId, uri, config, embedProv, { livenessProvider });
316
+ */
317
+ async function verifyWithProof(reference, liveImageUri, sdkConfig, embeddingProvider, options = {}) {
318
+ const { livenessProvider, imageDataProvider, ...configOptions } = options;
319
+ const config = mergeConfig(sdkConfig, configOptions);
320
+ // If ZK is not enabled, fall back to verify-only (match without proof)
321
+ if (!config.zk?.enabled) {
322
+ config.onLog?.({
323
+ level: "warn",
324
+ message: "ZK proof requested but ZK is not enabled in config, falling back to verify-only",
325
+ context: { stage: "zk_validation" },
326
+ });
327
+ return verifyOnly(reference, liveImageUri, sdkConfig, embeddingProvider, options);
328
+ }
329
+ // config.zk is guaranteed defined here — the early return above exits if !config.zk?.enabled
330
+ const zkConfig = config.zk;
331
+ config.onLog?.({
332
+ level: "info",
333
+ message: "Starting verification with ZK proof",
334
+ context: { liveImageUri },
335
+ });
336
+ // First, run normal verification
337
+ const outcome = await verifyOnly(reference, liveImageUri, sdkConfig, embeddingProvider, options);
338
+ // If verification failed before matching, return early
339
+ if (!outcome.match || !outcome.reference || !outcome.live) {
340
+ config.onLog?.({
341
+ level: "warn",
342
+ message: "Verification failed before ZK proof generation",
343
+ context: { error: outcome.error },
344
+ });
345
+ return outcome;
346
+ }
347
+ try {
348
+ // Generate ZK proof
349
+ config.onLog?.({
350
+ level: "debug",
351
+ message: "Generating ZK proof",
352
+ });
353
+ const id = (0, idGenerator_1.generateReferenceId)();
354
+ // Parse the last 8 characters of the ID to use as a 32-bit nonce
355
+ const nonce = parseInt(id.slice(-8), 16);
356
+ const startTime = Date.now();
357
+ const { proof, publicInputs } = await config.zk.engine.generateProof(outcome.reference.embedding, outcome.live.embedding, nonce);
358
+ config.onLog?.({
359
+ level: "debug",
360
+ message: "Verifying ZK proof",
361
+ });
362
+ const verified = await config.zk.engine.verifyProof(proof, publicInputs);
363
+ config.onLog?.({
364
+ level: "debug",
365
+ message: "Computing proof hash",
366
+ });
367
+ const hash = await config.zk.engine.getProofHash(proof);
368
+ const zkProof = {
369
+ proof,
370
+ publicInputs,
371
+ hash,
372
+ verified,
373
+ timestamp: Date.now(),
374
+ sizeBytes: new TextEncoder().encode(proof).length,
375
+ };
376
+ config.onLog?.({
377
+ level: "info",
378
+ message: "ZK proof generated and verified",
379
+ context: {
380
+ verified,
381
+ hash,
382
+ durationMs: Date.now() - startTime,
383
+ },
384
+ });
385
+ // Build new outcome with ZK results — no mutation of the original object (C-12)
386
+ const zkOutcome = zkConfig.requiredForSuccess && !verified
387
+ ? {
388
+ ...outcome,
389
+ zkProof,
390
+ success: false,
391
+ error: {
392
+ code: "ZK_ERROR",
393
+ message: "ZK proof verification failed",
394
+ details: { stage: "zk_verification", zkProof },
395
+ },
396
+ }
397
+ : { ...outcome, zkProof };
398
+ // Optionally persist proof via storage adapter
399
+ if (config.storage?.saveProof && verified) {
400
+ config.onLog?.({
401
+ level: "debug",
402
+ message: "Persisting ZK proof",
403
+ });
404
+ await config.storage.saveProof({
405
+ referenceId: outcome.reference.referenceId,
406
+ zkProof,
407
+ });
408
+ }
409
+ return zkOutcome;
410
+ }
411
+ catch (error) {
412
+ const zkError = {
413
+ code: "ZK_ERROR",
414
+ message: error instanceof Error ? error.message : "ZK proof failed",
415
+ details: {
416
+ stage: "zk_generation",
417
+ originalError: String(error),
418
+ },
419
+ };
420
+ config.onLog?.({
421
+ level: "error",
422
+ message: "ZK proof generation failed",
423
+ context: { ...zkError.details, code: zkError.code },
424
+ });
425
+ // If ZK is required for success, mark as failure
426
+ if (zkConfig.requiredForSuccess) {
427
+ return {
428
+ ...outcome,
429
+ success: false,
430
+ error: zkError,
431
+ };
432
+ }
433
+ // Otherwise, return outcome with error but original success state
434
+ return {
435
+ ...outcome,
436
+ error: zkError,
437
+ };
438
+ }
439
+ }
440
+ function generateSecureId(prefix) {
441
+ return (0, idGenerator_1.generateReferenceId)().replace(/^ref_/, `${prefix}_`);
442
+ }
@@ -0,0 +1,69 @@
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 – ZK Proof Core
18
+ *
19
+ * Functions for generating and managing zero-knowledge proofs.
20
+ * This module provides standalone ZK proof generation for advanced use cases
21
+ * where the caller already has embeddings and just needs the proof.
22
+ */
23
+ import type { FloatVector, ZkProofSummary, ZkProofOptions, FaceZkRuntimeConfig } from "./types";
24
+ /**
25
+ * Generate a zero-knowledge proof from embeddings without running the full verification pipeline.
26
+ *
27
+ * This function focuses strictly on the cryptographic generation mechanism using the provided ZK engine.
28
+ * It is designed for advanced integrations where the caller computes embeddings manually or handles their own camera lifecycles.
29
+ *
30
+ * **ZK Context:** Plonky3 WASM circuits enforce that the Euclidean distance between `referenceEmbedding` and `liveEmbedding` does not exceed the globally compiled threshold. If it does, proof generation fails cryptographically, returning a structured `ZK_ERROR`.
31
+ *
32
+ * @param {FloatVector} referenceEmbedding - Trusted reference face embedding.
33
+ * @param {FloatVector} liveEmbedding - Face embedding derived from the current live capture.
34
+ * @param {FaceZkRuntimeConfig} sdkConfig - Global SDK configuration requiring `zk.enabled = true` and an injected `zk.engine`.
35
+ * @param {ZkProofOptions} options - Options containing optional `nonce`. If missing, cryptographically secure nonce is auto-generated.
36
+ * @returns {Promise<ZkProofSummary>} Cryptographic proof, inputs, and the verified hash.
37
+ * @throws {SdkError} Throws `ZK_ERROR` if the embeddings do not meet the circuit threshold, if vectors mismatch, or if ZK is not enabled.
38
+ *
39
+ * @example
40
+ * try {
41
+ * const summary = await generateZkProofOnly(refEmbed, liveEmbed, config, {});
42
+ * console.log(`Proof generated. Hash: ${summary.hash}`);
43
+ * } catch (error) {
44
+ * console.error("Proof failed (threshold not met):", error);
45
+ * }
46
+ */
47
+ export declare function generateZkProofOnly(referenceEmbedding: FloatVector, liveEmbedding: FloatVector, sdkConfig: FaceZkRuntimeConfig, options: ZkProofOptions): Promise<ZkProofSummary>;
48
+ /**
49
+ * Helper function to generate a ZK proof and persist it securely using the active `StorageAdapter`.
50
+ *
51
+ * **Crypto Context:** The proof is persisted only if local verification against the generated public inputs passes. Unverified proofs will not be persisted to prevent storage poisoning.
52
+ *
53
+ * @param {string} referenceId - The opaque ID linking this proof to the enrolled reference.
54
+ * @param {FloatVector} referenceEmbedding - Trusted reference face embedding.
55
+ * @param {FloatVector} liveEmbedding - Live face embedding.
56
+ * @param {FaceZkRuntimeConfig} sdkConfig - SDK configuration including the configured `storage` adapter.
57
+ * @param {ZkProofOptions} options - Options containing optional `nonce`.
58
+ * @returns {Promise<{ summary: ZkProofSummary; proofId?: string }>} The generated ZK summary, and the storage handle if successfully persisted.
59
+ *
60
+ * @example
61
+ * const { summary, proofId } = await generateAndPersistZkProof(
62
+ * 'user-1234', refEmbed, liveEmbed, config, {}
63
+ * );
64
+ * console.log(`Proof stored under handle: ${proofId}`);
65
+ */
66
+ export declare function generateAndPersistZkProof(referenceId: string, referenceEmbedding: FloatVector, liveEmbedding: FloatVector, sdkConfig: FaceZkRuntimeConfig, options: ZkProofOptions): Promise<{
67
+ summary: ZkProofSummary;
68
+ proofId?: string;
69
+ }>;