@jupitermetalabs/face-zk-sdk 0.1.0 → 0.3.1

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 CHANGED
@@ -6,6 +6,7 @@ A standalone React Native and Web SDK for face verification and Zero-Knowledge (
6
6
 
7
7
  - **Face Matching**: High-accuracy face embedding comparison.
8
8
  - **Liveness Detection**: Interactive liveness checks with antispoofing protection.
9
+ - **Demographic Analysis**: Optional age and gender estimations alongside captures.
9
10
  - **ZK Proofs**: Generate and verify cryptographic proofs of identity without revealing facial biometric data.
10
11
  - **Platform Agnostic**: Works on iOS, Android (via Expo), and Web.
11
12
 
@@ -126,6 +127,7 @@ await initializeSdk({
126
127
  detection: { module: require('./assets/models/det_500m.onnx') },
127
128
  recognition: { module: require('./assets/models/w600k_mbf.onnx') },
128
129
  antispoof: { module: require('./assets/models/antispoof.onnx') },
130
+ ageGender: { module: require('./assets/models/genderage.onnx') },
129
131
  wasm: { module: require('./assets/wasm/zk_face_wasm_bg.wasm') },
130
132
  zkWorkerHtml: { module: require('./assets/zk-worker.html') },
131
133
  },
@@ -262,15 +262,16 @@ function checkGuidance(currentPose, currentGaze, faceWidth) {
262
262
  newStatus.arrowRight = false;
263
263
  isMatch = false;
264
264
  } else if (yawDiff > YAW_THRESHOLD) {
265
- statusText.innerText = "Turn Face Left";
266
- newStatus.arrowLeft = true;
267
- newStatus.arrowRight = false;
268
- isMatch = false;
269
- } else if (yawDiff < -YAW_THRESHOLD) {
265
+ // Front camera preview is mirrored; invert directional prompts to match user perception.
270
266
  statusText.innerText = "Turn Face Right";
271
267
  newStatus.arrowLeft = false;
272
268
  newStatus.arrowRight = true;
273
269
  isMatch = false;
270
+ } else if (yawDiff < -YAW_THRESHOLD) {
271
+ statusText.innerText = "Turn Face Left";
272
+ newStatus.arrowLeft = true;
273
+ newStatus.arrowRight = false;
274
+ isMatch = false;
274
275
  } else {
275
276
  // Yaw is correct. Check Gaze.
276
277
  // Assuming 0.5 is center.
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
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
+ "use strict";
18
+
19
+ /**
20
+ * Copies bundled WASM and worker assets into dist/assets/ after tsc compilation.
21
+ *
22
+ * The compiled JS in dist/react-native/hooks/ references these assets with
23
+ * relative paths like ../../assets/wasm/... which resolve to dist/assets/wasm/
24
+ * at runtime (Metro bundler resolves relative to the compiled file location).
25
+ *
26
+ * Runs automatically as part of the "build" npm script.
27
+ * Safe to run multiple times (idempotent).
28
+ *
29
+ * Files copied:
30
+ * assets/wasm/zk_face_wasm_bg.wasm → dist/assets/wasm/zk_face_wasm_bg.wasm
31
+ * assets/zk-worker.html → dist/assets/zk-worker.html
32
+ */
33
+
34
+ const fs = require("fs");
35
+ const path = require("path");
36
+
37
+ const ROOT = path.resolve(__dirname, "..");
38
+ const DIST_ASSETS = path.resolve(ROOT, "dist/assets");
39
+
40
+ const COPIES = [
41
+ { src: "assets/wasm/zk_face_wasm_bg.wasm", dst: "wasm/zk_face_wasm_bg.wasm" },
42
+ { src: "assets/zk-worker.html", dst: "zk-worker.html" },
43
+ ];
44
+
45
+ fs.mkdirSync(path.join(DIST_ASSETS, "wasm"), { recursive: true });
46
+
47
+ for (const { src, dst } of COPIES) {
48
+ const srcPath = path.join(ROOT, src);
49
+ const dstPath = path.join(DIST_ASSETS, dst);
50
+
51
+ if (!fs.existsSync(srcPath)) {
52
+ process.stderr.write(`[copy-dist-assets] WARNING: ${src} not found — skipping\n`);
53
+ continue;
54
+ }
55
+
56
+ fs.copyFileSync(srcPath, dstPath);
57
+ const size = Math.round(fs.statSync(dstPath).size / 1024);
58
+ process.stdout.write(`[copy-dist-assets] copied ${src} → dist/assets/${dst} (${size} KB)\n`);
59
+ }
@@ -0,0 +1,472 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>ZK Worker</title>
7
+ </head>
8
+
9
+ <body>
10
+ <div id="status">Initializing...</div>
11
+ <script>
12
+ // ==========================================
13
+ // WASM Bindings (Full set from pkg-web/zk_face_wasm.js)
14
+ // BUILD TIMESTAMP: 2025-12-22T17:00:00 [CACHE_BUST]
15
+ // ==========================================
16
+ const BUILD_TIMESTAMP = "2025-12-22T17:00:00";
17
+ let wasm;
18
+
19
+ const heap = new Array(128).fill(undefined);
20
+ heap.push(undefined, null, true, false);
21
+
22
+ function getObject(idx) { return heap[idx]; }
23
+
24
+ let heap_next = heap.length;
25
+
26
+ function dropObject(idx) {
27
+ if (idx < 132) return;
28
+ heap[idx] = heap_next;
29
+ heap_next = idx;
30
+ }
31
+
32
+ function takeObject(idx) {
33
+ const ret = getObject(idx);
34
+ dropObject(idx);
35
+ return ret;
36
+ }
37
+
38
+
39
+
40
+ function isLikeNone(x) {
41
+ return x === undefined || x === null;
42
+ }
43
+
44
+ // --- Memory Views ---
45
+ let cachedDataViewMemory0 = null;
46
+ function getDataViewMemory0() {
47
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
48
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
49
+ }
50
+ return cachedDataViewMemory0;
51
+ }
52
+
53
+ let cachedUint8ArrayMemory0 = null;
54
+ function getUint8ArrayMemory0() {
55
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
56
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
57
+ }
58
+ return cachedUint8ArrayMemory0;
59
+ }
60
+
61
+ function getArrayU8FromWasm0(ptr, len) {
62
+ ptr = ptr >>> 0;
63
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
64
+ }
65
+
66
+ // --- String Handling ---
67
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
68
+ cachedTextDecoder.decode();
69
+
70
+ function getStringFromWasm0(ptr, len) {
71
+ ptr = ptr >>> 0;
72
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
73
+ }
74
+
75
+ let WASM_VECTOR_LEN = 0;
76
+ let cachedTextEncoder = new TextEncoder('utf-8');
77
+
78
+ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
79
+ ? function (arg, view) {
80
+ return cachedTextEncoder.encodeInto(arg, view);
81
+ }
82
+ : function (arg, view) {
83
+ const buf = cachedTextEncoder.encode(arg);
84
+ view.set(buf);
85
+ return {
86
+ read: arg.length,
87
+ written: buf.length
88
+ };
89
+ });
90
+
91
+ function passStringToWasm0(arg, malloc, realloc) {
92
+ if (realloc === undefined) {
93
+ const buf = cachedTextEncoder.encode(arg);
94
+ const ptr = malloc(buf.length, 1) >>> 0;
95
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
96
+ WASM_VECTOR_LEN = buf.length;
97
+ return ptr;
98
+ }
99
+
100
+ let len = arg.length;
101
+ let ptr = malloc(len, 1) >>> 0;
102
+ const mem = getUint8ArrayMemory0();
103
+ let offset = 0;
104
+
105
+ for (; offset < len; offset++) {
106
+ const code = arg.charCodeAt(offset);
107
+ if (code > 0x7F) break;
108
+ mem[ptr + offset] = code;
109
+ }
110
+ if (offset !== len) {
111
+ if (offset !== 0) {
112
+ arg = arg.slice(offset);
113
+ }
114
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
115
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
116
+ const ret = encodeString(arg, view);
117
+ offset += ret.written;
118
+ }
119
+ WASM_VECTOR_LEN = offset;
120
+ return ptr;
121
+ }
122
+
123
+
124
+
125
+ function takeFromExternrefTable0(idx) {
126
+ const value = wasm.__wbindgen_externrefs.get(idx);
127
+ wasm.__externref_table_dealloc(idx);
128
+ return value;
129
+ }
130
+
131
+ // --- THE FULL IMPORTS OBJECT ---
132
+ function getImports() {
133
+ const wbg = {};
134
+
135
+ wbg.__wbg_error_7534b8e9a36f1ab4 = function (arg0, arg1) {
136
+ let deferred0_0;
137
+ let deferred0_1;
138
+ try {
139
+ deferred0_0 = arg0;
140
+ deferred0_1 = arg1;
141
+ console.error(getStringFromWasm0(arg0, arg1));
142
+ } finally {
143
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
144
+ }
145
+ };
146
+ wbg.__wbg_new_8a6f238a6ece86ea = function () {
147
+ const ret = new Error();
148
+ return ret;
149
+ };
150
+ wbg.__wbg_stack_0ed75d68575b0f3c = function (arg0, arg1) {
151
+ const ret = arg1.stack;
152
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
153
+ const len1 = WASM_VECTOR_LEN;
154
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
155
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
156
+ };
157
+ wbg.__wbindgen_cast_2241b6af4c4b2941 = function (arg0, arg1) {
158
+ const ret = getStringFromWasm0(arg0, arg1);
159
+ return ret;
160
+ };
161
+ wbg.__wbindgen_init_externref_table = function () {
162
+ const table = wasm.__wbindgen_externrefs;
163
+ const offset = table.grow(4);
164
+ table.set(0, undefined);
165
+ table.set(offset + 0, undefined);
166
+ table.set(offset + 1, null);
167
+ table.set(offset + 2, true);
168
+ table.set(offset + 3, false);
169
+ };
170
+
171
+ // DUAL-MODE RETURN
172
+ return {
173
+ wbg: wbg,
174
+ './zk_face_wasm_bg.js': wbg
175
+ };
176
+ }
177
+
178
+ function finalizeInit(instance, module) {
179
+ wasm = instance.exports;
180
+ cachedUint8ArrayMemory0 = null;
181
+ cachedDataViewMemory0 = null;
182
+
183
+ // Critical: Initialize the externref table
184
+ wasm.__wbindgen_start();
185
+ return wasm;
186
+ }
187
+
188
+ // ==========================================
189
+ // Main Application Logic
190
+ // ==========================================
191
+
192
+ function log(msg) {
193
+ console.log(msg);
194
+ if (window.ReactNativeWebView) {
195
+ window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'log', message: msg }));
196
+ }
197
+ }
198
+
199
+ function base64ToUint8Array(base64) {
200
+ // Basic Base64 decoding - ensure whitespace is removed
201
+ const cleanBase64 = base64.replace(/\s/g, '');
202
+ const binary_string = window.atob(cleanBase64);
203
+ const len = binary_string.length;
204
+ const bytes = new Uint8Array(len);
205
+ for (let i = 0; i < len; i++) {
206
+ bytes[i] = binary_string.charCodeAt(i);
207
+ }
208
+ return bytes;
209
+ }
210
+
211
+ // Initialize WASM (Plonky3 - No Proving Key Needed!)
212
+ async function initWasm(base64Data) {
213
+ try {
214
+ log('[ZK Worker] Instantiating Plonky3 WASM... Build: ' + (typeof BUILD_TIMESTAMP !== 'undefined' ? BUILD_TIMESTAMP : 'UNKNOWN'));
215
+ const bytes = base64ToUint8Array(base64Data);
216
+
217
+ // Use the imports from our inlined glue code
218
+ const imports = getImports();
219
+ log('[ZK Worker] Imports prepared. Keys: ' + Object.keys(imports).join(', '));
220
+
221
+ const { instance, module } = await WebAssembly.instantiate(bytes, imports);
222
+ finalizeInit(instance, module);
223
+
224
+ // No proving key needed with Plonky3 STARKs!
225
+ log('[ZK Worker] ✅ Plonky3 WASM Loaded (no keys needed!)');
226
+ document.getElementById('status').innerText = 'Ready - Plonky3';
227
+
228
+ window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'wasm_initialized' }));
229
+
230
+ } catch (err) {
231
+ log('[ZK Worker] FATAL: ' + err.message);
232
+ if (err.stack) log(err.stack);
233
+ document.getElementById('status').innerText = 'Error: ' + err.message;
234
+ }
235
+ }
236
+
237
+ async function generateProof(payload) {
238
+ if (!wasm) throw new Error("WASM not initialized");
239
+
240
+ const { alloc, dealloc, generate_face_proof_from_ptrs } = wasm;
241
+
242
+ // 1. Prepare Data
243
+ const storedFloats = new Float64Array(payload.storedEmbedding);
244
+ const capturedFloats = new Float64Array(payload.capturedEmbedding);
245
+
246
+ const storedSize = storedFloats.length * 8;
247
+ const capturedSize = capturedFloats.length * 8;
248
+
249
+ // 2. Alloc
250
+ const storedPtr = alloc(storedSize);
251
+ const capturedPtr = alloc(capturedSize);
252
+
253
+ try {
254
+ // 3. Write embeddings to WASM memory
255
+ // Use Uint8Array to copy bytes directly, avoiding "Byte offset is not aligned" errors
256
+ // if the allocator returns an address that isn't 8-byte aligned.
257
+ new Uint8Array(wasm.memory.buffer).set(new Uint8Array(storedFloats.buffer), storedPtr);
258
+ new Uint8Array(wasm.memory.buffer).set(new Uint8Array(capturedFloats.buffer), capturedPtr);
259
+
260
+ log('[ZK Worker] === Diagnostic Info ===');
261
+
262
+ // Prepare all parameters
263
+ const thresholdValue = payload.threshold || 0.6;
264
+ const nonceValue = BigInt(Date.now());
265
+
266
+ // Log parameter values
267
+ log(`Stored: ptr=${storedPtr}, count=${storedFloats.length}, first value=${storedFloats[0]}`);
268
+ log(`Captured: ptr=${capturedPtr}, count=${capturedFloats.length}, first value=${capturedFloats[0]}`);
269
+ log(`Threshold: ${thresholdValue}`);
270
+ log(`Nonce: ${nonceValue}`);
271
+
272
+ // Log parameter TYPES for debugging
273
+ log('[ZK Worker] === Parameter Types ===');
274
+ log(`storedPtr type: ${typeof storedPtr}, value: ${storedPtr}`);
275
+ log(`storedFloats.length type: ${typeof storedFloats.length}, value: ${storedFloats.length}`);
276
+ log(`capturedPtr type: ${typeof capturedPtr}, value: ${capturedPtr}`);
277
+ log(`capturedFloats.length type: ${typeof capturedFloats.length}, value: ${capturedFloats.length}`);
278
+ log(`threshold type: ${typeof thresholdValue}, value: ${thresholdValue}`);
279
+ log(`nonce type: ${typeof nonceValue}, value: ${nonceValue}`);
280
+
281
+ log('[ZK Worker] Calling Rust (Plonky3 - no PK needed)...');
282
+
283
+ const start = performance.now();
284
+
285
+ let ret;
286
+ try {
287
+ // 4. Call Rust (no PK parameters!)
288
+ // Plonky3 STARKs don't need proving keys
289
+ // Convert nonce to BigInt to match Rust u64 type
290
+ ret = generate_face_proof_from_ptrs(
291
+ storedPtr, storedFloats.length,
292
+ capturedPtr, capturedFloats.length,
293
+ thresholdValue,
294
+ nonceValue
295
+ );
296
+ log('[ZK Worker] Rust call returned successfully');
297
+ log(`[ZK Worker] Return value type: ${typeof ret}, isArray: ${Array.isArray(ret)}`);
298
+ if (Array.isArray(ret)) {
299
+ log(`[ZK Worker] Return array length: ${ret.length}`);
300
+ ret.forEach((val, idx) => {
301
+ log(`[ZK Worker] ret[${idx}] type: ${typeof val}, value: ${val}`);
302
+ });
303
+ }
304
+ } catch (wasmErr) {
305
+ log('[ZK Worker] WASM threw during call: ' + wasmErr.message);
306
+ log('[ZK Worker] Error stack: ' + (wasmErr.stack || 'No stack available'));
307
+ throw wasmErr;
308
+ }
309
+
310
+ var ptr = ret[0];
311
+ var len = ret[1];
312
+ var errPtr = ret[2];
313
+ var isErr = ret[3] !== 0;
314
+
315
+ if (isErr) {
316
+ // Extract error from externref table
317
+ const errorObj = takeFromExternrefTable0(errPtr);
318
+ throw new Error(errorObj);
319
+ }
320
+
321
+ // If success, read string from memory
322
+ const resultJson = getStringFromWasm0(ptr, len);
323
+
324
+ // Free the result string memory
325
+ if (wasm.__wbindgen_free) {
326
+ wasm.__wbindgen_free(ptr, len, 1);
327
+ }
328
+
329
+ const end = performance.now();
330
+ log(`[ZK Worker] ✅ Plonky3 proof generated in ${(end - start).toFixed(2)}ms`);
331
+
332
+ return resultJson;
333
+
334
+ } finally {
335
+ dealloc(storedPtr, storedSize);
336
+ dealloc(capturedPtr, capturedSize);
337
+ }
338
+ }
339
+
340
+ function handleMessage(event) {
341
+ try {
342
+ const msg = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
343
+
344
+ if (msg.type === 'init_wasm') {
345
+ initWasm(msg.payload.wasmBase64); // Access via payload
346
+ }
347
+ else if (msg.type === 'generate_proof') {
348
+ generateProof(msg.payload)
349
+ .then(result => {
350
+ log('[ZK Worker] Raw Result Type: ' + typeof result);
351
+ log('[ZK Worker] Raw Result Length: ' + result.length);
352
+ log('[ZK Worker] Raw Result Start: ' + result.substring(0, 50));
353
+ log('[ZK Worker] Raw Result End: ' + result.substring(result.length - 50));
354
+
355
+ let parsed;
356
+ try {
357
+ parsed = JSON.parse(result);
358
+ log('[ZK Worker] Parse Success. Keys: ' + Object.keys(parsed).join(', '));
359
+ if (parsed.proof) log('[ZK Worker] parsed.proof length: ' + parsed.proof.length);
360
+ } catch (e) {
361
+ log('[ZK Worker] JSON Parse Error: ' + e.message);
362
+ }
363
+
364
+ window.ReactNativeWebView.postMessage(JSON.stringify({
365
+ type: 'proof_result',
366
+ data: parsed
367
+ }));
368
+ })
369
+ .catch(err => {
370
+ // Handle WASM panics properly
371
+ let errorMsg = err.toString();
372
+ if (err.message) errorMsg = err.message;
373
+
374
+ window.ReactNativeWebView.postMessage(JSON.stringify({
375
+ type: 'error',
376
+ error: errorMsg
377
+ }));
378
+ });
379
+ }
380
+ else if (msg.type === 'verify_proof') {
381
+ if (!wasm) throw new Error("WASM not initialized");
382
+ try {
383
+ const { verify_face_proof, __wbindgen_free } = wasm;
384
+ const payload = msg.payload;
385
+
386
+ // LOG THE RAW PAYLOAD GOING TO RUST
387
+ log('[ZK Worker] Verify Payload Keys: ' + Object.keys(payload).join(', '));
388
+ if(payload.public_inputs) {
389
+ log('[ZK Worker] Verify Public Inputs: ' + JSON.stringify(payload.public_inputs));
390
+ }
391
+
392
+ const proofJson = JSON.stringify(payload);
393
+
394
+ log('[ZK Worker] Verifying proof... Input JSON len: ' + proofJson.length);
395
+ const sPtr = passStringToWasm0(proofJson, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
396
+ const sLen = WASM_VECTOR_LEN;
397
+
398
+ const ret = verify_face_proof(sPtr, sLen);
399
+
400
+ var ptr = ret[0];
401
+ var len = ret[1];
402
+ var errPtr = ret[2];
403
+ var isErr = ret[3] !== 0;
404
+
405
+ if (isErr) {
406
+ const errorObj = takeFromExternrefTable0(errPtr);
407
+ throw new Error(errorObj);
408
+ }
409
+
410
+ const resultJson = getStringFromWasm0(ptr, len);
411
+ log('[ZK Worker] Verify Result raw JSON: ' + resultJson);
412
+
413
+ if (__wbindgen_free) __wbindgen_free(ptr, len, 1);
414
+
415
+ window.ReactNativeWebView.postMessage(JSON.stringify({
416
+ type: 'verify_result',
417
+ data: JSON.parse(resultJson)
418
+ }));
419
+ } catch (e) {
420
+ log('[ZK Worker] Verify Error: ' + e.message);
421
+ window.ReactNativeWebView.postMessage(JSON.stringify({
422
+ type: 'error',
423
+ error: e.message
424
+ }));
425
+ }
426
+ }
427
+ else if (msg.type === 'get_proof_hash') {
428
+ if (!wasm) throw new Error("WASM not initialized");
429
+ try {
430
+ const { get_proof_hash, __wbindgen_free } = wasm;
431
+ const proofJson = JSON.stringify(msg.payload);
432
+
433
+ const sPtr = passStringToWasm0(proofJson, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
434
+ const sLen = WASM_VECTOR_LEN;
435
+
436
+ const ret = get_proof_hash(sPtr, sLen);
437
+
438
+ var ptr = ret[0];
439
+ var len = ret[1];
440
+
441
+ const hashStr = getStringFromWasm0(ptr, len);
442
+ if (__wbindgen_free) __wbindgen_free(ptr, len, 1);
443
+
444
+ window.ReactNativeWebView.postMessage(JSON.stringify({
445
+ type: 'hash_result',
446
+ hash: hashStr
447
+ }));
448
+ } catch (e) {
449
+ window.ReactNativeWebView.postMessage(JSON.stringify({
450
+ type: 'error',
451
+ error: e.message
452
+ }));
453
+ }
454
+ }
455
+ } catch (e) {
456
+ log('Worker JSON Error: ' + e.message);
457
+ }
458
+ }
459
+
460
+ window.addEventListener('message', handleMessage);
461
+ document.addEventListener('message', handleMessage);
462
+
463
+ if (window.ReactNativeWebView) {
464
+ setTimeout(() => {
465
+ window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'ready' }));
466
+ }, 100);
467
+ }
468
+ </script>
469
+ </body>
470
+
471
+ </html>
472
+
@@ -25,6 +25,7 @@ export declare const DEFAULT_MODEL_FILES: {
25
25
  readonly detection: "det_500m.onnx";
26
26
  readonly recognition: "w600k_mbf.onnx";
27
27
  readonly antispoof: "antispoof.onnx";
28
+ readonly ageGender: "genderage.onnx";
28
29
  readonly wasm: "zk_face_wasm_bg.wasm";
29
30
  readonly zkWorkerHtml: "zk-worker.html";
30
31
  };
@@ -44,6 +45,7 @@ export declare function buildModelUrls(base: string, files?: FaceZkSetupConfig["
44
45
  detection: string;
45
46
  recognition: string;
46
47
  antispoof: string;
48
+ ageGender: string;
47
49
  wasm: string;
48
50
  zkWorkerHtml: string;
49
51
  };
@@ -18,6 +18,7 @@ export const DEFAULT_MODEL_FILES = {
18
18
  detection: "det_500m.onnx",
19
19
  recognition: "w600k_mbf.onnx",
20
20
  antispoof: "antispoof.onnx",
21
+ ageGender: "genderage.onnx",
21
22
  wasm: "zk_face_wasm_bg.wasm",
22
23
  zkWorkerHtml: "zk-worker.html",
23
24
  };
@@ -49,6 +50,7 @@ export function buildModelUrls(base, files) {
49
50
  detection: `${b}/${f.detection}`,
50
51
  recognition: `${b}/${f.recognition}`,
51
52
  antispoof: `${b}/${f.antispoof}`,
53
+ ageGender: `${b}/${f.ageGender}`,
52
54
  wasm: `${b}/${f.wasm}`,
53
55
  zkWorkerHtml: `${b}/${f.zkWorkerHtml}`,
54
56
  };
@@ -39,6 +39,8 @@ export interface FaceZkModelsConfig {
39
39
  recognition: ModelSource;
40
40
  /** Anti-spoof model – antispoof.onnx (optional; falls back to bundled) */
41
41
  antispoof?: ModelSource;
42
+ /** Age/Gender model – genderage.onnx (optional) */
43
+ ageGender?: ModelSource;
42
44
  /** ZK proof WASM binary – zk_face_wasm_bg.wasm (optional; falls back to bundled) */
43
45
  wasm?: ModelSource;
44
46
  /** ZK proof worker HTML – zk-worker.html (optional; falls back to bundled) */
@@ -115,6 +117,7 @@ export interface FaceZkSetupConfig {
115
117
  detection?: string;
116
118
  recognition?: string;
117
119
  antispoof?: string;
120
+ ageGender?: string;
118
121
  wasm?: string;
119
122
  zkWorkerHtml?: string;
120
123
  };
@@ -40,6 +40,8 @@ export interface FaceEmbeddingProvider {
40
40
  roll: number;
41
41
  };
42
42
  message?: string;
43
+ gender?: "Male" | "Female" | "Unknown";
44
+ age?: number;
43
45
  }>;
44
46
  }
45
47
  /**
@@ -14,15 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { isSdkError } from "./types";
17
- /**
18
- * Generate a unique reference ID.
19
- * Uses timestamp + random string for uniqueness.
20
- */
21
- function generateReferenceId() {
22
- const timestamp = Date.now();
23
- const random = Math.random().toString(36).slice(2, 10);
24
- return `ref_${timestamp}_${random}`;
25
- }
17
+ import { generateReferenceId } from "./idGenerator";
26
18
  /**
27
19
  * Creates a reference template from an image URI to be used in future verification attempts.
28
20
  *
@@ -0,0 +1,11 @@
1
+ import type { ReferenceId } from "./types";
2
+ /**
3
+ * Generates a unique reference ID using platform-native crypto.
4
+ * Format: ref_<12-char-ms-timestamp-hex>_<20-char-random-hex>
5
+ *
6
+ * Uses crypto.getRandomValues() — available natively in:
7
+ * - Node.js 15.7+ (Jest test environment)
8
+ * - All modern browsers
9
+ * - Hermes (React Native 0.71+) — no polyfill required
10
+ */
11
+ export declare function generateReferenceId(): ReferenceId;