@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 +2 -0
- package/assets/face-guidance/pose-guidance.js.txt +6 -5
- package/cli/copy-dist-assets.js +59 -0
- package/dist/assets/wasm/zk_face_wasm_bg.wasm +0 -0
- package/dist/assets/zk-worker.html +472 -0
- package/dist/config/defaults.d.ts +2 -0
- package/dist/config/defaults.js +2 -0
- package/dist/config/types.d.ts +3 -0
- package/dist/core/enrollment-core.d.ts +2 -0
- package/dist/core/enrollment-core.js +1 -9
- package/dist/core/idGenerator.d.ts +11 -0
- package/dist/core/idGenerator.js +29 -0
- package/dist/core/types.d.ts +4 -0
- package/dist/core/verification-core.js +7 -3
- package/dist/react-native/components/FacePoseGuidanceWebView.js +1 -1
- package/dist/react-native/components/OnnxRuntimeWebView.d.ts +5 -1
- package/dist/react-native/components/OnnxRuntimeWebView.js +83 -4
- package/dist/react-native/services/FaceRecognition.d.ts +6 -0
- package/dist/react-native/services/FaceRecognition.js +96 -1
- package/dist/storage/defaultStorageAdapter.d.ts +2 -2
- package/dist/storage/defaultStorageAdapter.js +23 -18
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
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
|
-
|
|
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
|
+
}
|
|
Binary file
|
|
@@ -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
|
};
|
package/dist/config/defaults.js
CHANGED
|
@@ -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
|
};
|
package/dist/config/types.d.ts
CHANGED
|
@@ -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
|
};
|
|
@@ -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;
|