@verifiedonchain-protocol/sdk 0.1.1 → 0.1.2
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/dist/index.d.ts +3 -1
- package/dist/index.js +74 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -259,6 +259,8 @@ declare function checkFaceOcclusionRaw(landmarks: NormalizedLandmark[], ctx: Can
|
|
|
259
259
|
/** @deprecated Use tracker + checkFaceOcclusionRaw */
|
|
260
260
|
declare function checkFaceOcclusion(landmarks: NormalizedLandmark[], ctx: CanvasRenderingContext2D, width: number, height: number): FaceOcclusionResult;
|
|
261
261
|
|
|
262
|
+
declare const EMBEDDING_DIMENSIONS = 1024;
|
|
263
|
+
declare const SIGNATURE_BITS = 256;
|
|
262
264
|
/**
|
|
263
265
|
* Generates deterministic random hyperplanes
|
|
264
266
|
* These chop the high-dimensional space into 256 regions
|
|
@@ -560,4 +562,4 @@ interface SocialConnectionProps {
|
|
|
560
562
|
}
|
|
561
563
|
declare const SocialConnection: React.FC<SocialConnectionProps>;
|
|
562
564
|
|
|
563
|
-
export { type AleoCircuitInputs, type AleoCircuitInputsComplete, type AleoEmbeddingChunks, type AleoSimHashInputs, BACKEND_PRIORITY, type BiometricSimHashPayload, CAPTURE_QUALITY, type CaptureQualityResult, type ChainType, type DataQuality, EmbeddingQuantizer, type FaceBox, type FaceOcclusionResult, FaceOcclusionTracker, FaceZKInner as FaceZK, type FlashColor, GlassesDetector, HUMAN_CONFIG, type LivenessData, LivenessEngine, type LivenessResult, MaskDetector, type NormalizedLandmark, type OcclusionCheckOptions, ProofOfHumanity, type RawFacialData, type RelayerConfig, type RelayerResponse, SocialConnection, VERIFICATION_CONSTANTS, type VerificationPayload, type VerificationResult, type VerifiedIdentityData, VerifiedOnchainFlow, type VerifiedOnchainFlowProps, VerifiedRelayerClient, type VisionModels, WalletSelection, averageEmbeddings, calculateHammingDistance, checkFaceOcclusion, checkFaceOcclusionRaw, computeSimHash, createCalibratedQuantizer, createLivenessData, evaluateCaptureQuality, extractRawFacialData, generateBiometricSimHash, generateHyperplanes, generateRandomSalt, getFaceBox, getFaceLuminance, getHyperplanes, getRegionLuminance, hammingDistance, normalizeEmbedding, processFacialDataForAleo, similarityFromDistance, useCameraStream, useVisionModels, validateDataQuality };
|
|
565
|
+
export { type AleoCircuitInputs, type AleoCircuitInputsComplete, type AleoEmbeddingChunks, type AleoSimHashInputs, BACKEND_PRIORITY, type BiometricSimHashPayload, CAPTURE_QUALITY, type CaptureQualityResult, type ChainType, type DataQuality, EMBEDDING_DIMENSIONS, EmbeddingQuantizer, type FaceBox, type FaceOcclusionResult, FaceOcclusionTracker, FaceZKInner as FaceZK, type FlashColor, GlassesDetector, HUMAN_CONFIG, type LivenessData, LivenessEngine, type LivenessResult, MaskDetector, type NormalizedLandmark, type OcclusionCheckOptions, ProofOfHumanity, type RawFacialData, type RelayerConfig, type RelayerResponse, SIGNATURE_BITS, SocialConnection, VERIFICATION_CONSTANTS, type VerificationPayload, type VerificationResult, type VerifiedIdentityData, VerifiedOnchainFlow, type VerifiedOnchainFlowProps, VerifiedRelayerClient, type VisionModels, WalletSelection, averageEmbeddings, calculateHammingDistance, checkFaceOcclusion, checkFaceOcclusionRaw, computeSimHash, createCalibratedQuantizer, createLivenessData, evaluateCaptureQuality, extractRawFacialData, generateBiometricSimHash, generateHyperplanes, generateRandomSalt, getFaceBox, getFaceLuminance, getHyperplanes, getRegionLuminance, hammingDistance, normalizeEmbedding, processFacialDataForAleo, similarityFromDistance, useCameraStream, useVisionModels, validateDataQuality };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// src/core/biometrics/simHash.ts
|
|
2
2
|
var SEED_PREFIX = "HumanityGen_SimHash_Hyperplane_";
|
|
3
|
+
var EMBEDDING_DIMENSIONS = 1024;
|
|
4
|
+
var SIGNATURE_BITS = 256;
|
|
3
5
|
function generateHyperplanes(dimensions, count) {
|
|
4
6
|
const hyperplanes = [];
|
|
5
7
|
for (let i = 0; i < count; i++) {
|
|
@@ -34,12 +36,24 @@ function simpleHash(str) {
|
|
|
34
36
|
}
|
|
35
37
|
function computeSimHash(embedding, hyperplanes) {
|
|
36
38
|
if (embedding.length === 0) return "";
|
|
37
|
-
const
|
|
39
|
+
const hyperplaneDim = hyperplanes[0].length;
|
|
40
|
+
let workingEmbedding = embedding;
|
|
41
|
+
if (embedding.length !== hyperplaneDim) {
|
|
42
|
+
console.warn(
|
|
43
|
+
`\u26A0\uFE0F SimHash dimension mismatch: embedding has ${embedding.length} dims, hyperplanes expect ${hyperplaneDim}. ` + (embedding.length < hyperplaneDim ? `Zero-padding embedding with ${hyperplaneDim - embedding.length} extra dims.` : `Truncating embedding to ${hyperplaneDim} dims.`)
|
|
44
|
+
);
|
|
45
|
+
if (embedding.length < hyperplaneDim) {
|
|
46
|
+
workingEmbedding = [...embedding, ...new Array(hyperplaneDim - embedding.length).fill(0)];
|
|
47
|
+
} else {
|
|
48
|
+
workingEmbedding = embedding.slice(0, hyperplaneDim);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const dims = hyperplaneDim;
|
|
38
52
|
let signatureBytes = new Uint8Array(hyperplanes.length / 8);
|
|
39
53
|
for (let i = 0; i < hyperplanes.length; i++) {
|
|
40
54
|
let dotProduct = 0;
|
|
41
55
|
for (let j = 0; j < dims; j++) {
|
|
42
|
-
dotProduct +=
|
|
56
|
+
dotProduct += workingEmbedding[j] * hyperplanes[i][j];
|
|
43
57
|
}
|
|
44
58
|
if (dotProduct > 0) {
|
|
45
59
|
const byteIndex = Math.floor(i / 8);
|
|
@@ -72,7 +86,7 @@ function hammingDistance(hex1, hex2) {
|
|
|
72
86
|
return distance;
|
|
73
87
|
}
|
|
74
88
|
var cachedHyperplanes = null;
|
|
75
|
-
function getHyperplanes(dimensions =
|
|
89
|
+
function getHyperplanes(dimensions = EMBEDDING_DIMENSIONS, count = SIGNATURE_BITS) {
|
|
76
90
|
if (!cachedHyperplanes || cachedHyperplanes[0].length !== dimensions || cachedHyperplanes.length !== count) {
|
|
77
91
|
console.time("Generating Hyperplanes");
|
|
78
92
|
cachedHyperplanes = generateHyperplanes(dimensions, count);
|
|
@@ -110,7 +124,7 @@ function averageEmbeddings(embeddings) {
|
|
|
110
124
|
return { index, distance: Math.sqrt(sumSq) };
|
|
111
125
|
});
|
|
112
126
|
distances.sort((a, b) => a.distance - b.distance);
|
|
113
|
-
const keepCount = Math.max(1, Math.floor(count * 0.
|
|
127
|
+
const keepCount = Math.max(1, Math.floor(count * 0.85));
|
|
114
128
|
const bestIndices = new Set(distances.slice(0, keepCount).map((d) => d.index));
|
|
115
129
|
const finalAvg = new Array(dim).fill(0);
|
|
116
130
|
for (let i = 0; i < count; i++) {
|
|
@@ -123,7 +137,7 @@ function averageEmbeddings(embeddings) {
|
|
|
123
137
|
for (let d = 0; d < dim; d++) {
|
|
124
138
|
finalAvg[d] /= keepCount;
|
|
125
139
|
}
|
|
126
|
-
console.log(`Averaged ${keepCount} clean embeddings (discarded ${count - keepCount} outliers)`);
|
|
140
|
+
console.log(`Averaged ${keepCount}/${count} clean embeddings (discarded ${count - keepCount} outliers)`);
|
|
127
141
|
return finalAvg;
|
|
128
142
|
}
|
|
129
143
|
function safeNumber(value, fallback = 0) {
|
|
@@ -507,7 +521,7 @@ function generateBiometricSimHash(face, livenessData, averagedEmbedding) {
|
|
|
507
521
|
const rawData = extractRawFacialData(face);
|
|
508
522
|
const sourceEmbedding = averagedEmbedding && averagedEmbedding.length > 0 ? averagedEmbedding : rawData.faceEmbedding;
|
|
509
523
|
const normalized = normalizeEmbedding(sourceEmbedding);
|
|
510
|
-
const hyperplanes = getHyperplanes(
|
|
524
|
+
const hyperplanes = getHyperplanes(normalized.length || EMBEDDING_DIMENSIONS);
|
|
511
525
|
const simHashHex = computeSimHash(normalized, hyperplanes);
|
|
512
526
|
const hasBlink = livenessData.blinkCount >= 1;
|
|
513
527
|
const hasEmotionTransition = livenessData.emotionTransitions.length > 0;
|
|
@@ -1570,10 +1584,16 @@ var FaceZKInner = ({
|
|
|
1570
1584
|
}
|
|
1571
1585
|
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
1572
1586
|
};
|
|
1573
|
-
const captureMultipleEmbeddings = async (video, sampleCount = 10, intervalMs = 200) => {
|
|
1587
|
+
const captureMultipleEmbeddings = async (video, sampleCount = 10, intervalMs = 200, assertFaceClear) => {
|
|
1574
1588
|
const embeddings = [];
|
|
1575
1589
|
console.log(`\u{1F4F8} Starting multi-frame capture: ${sampleCount} samples, ${intervalMs}ms apart`);
|
|
1576
1590
|
for (let i = 0; i < sampleCount; i++) {
|
|
1591
|
+
if (assertFaceClear) {
|
|
1592
|
+
const status = assertFaceClear();
|
|
1593
|
+
if (!status.aligned) {
|
|
1594
|
+
throw new Error(status.message || "Face obstruction detected during capture");
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1577
1597
|
try {
|
|
1578
1598
|
const emb = await extractFaceEmbedding(video);
|
|
1579
1599
|
embeddings.push(emb);
|
|
@@ -1815,8 +1835,22 @@ var FaceZKInner = ({
|
|
|
1815
1835
|
setLiveFeedback(null);
|
|
1816
1836
|
const yaw = getYawDegrees(latestMatrix.current);
|
|
1817
1837
|
if (condition(yaw)) {
|
|
1818
|
-
|
|
1819
|
-
|
|
1838
|
+
const holdUntil = Date.now() + 300;
|
|
1839
|
+
let holdBroken = false;
|
|
1840
|
+
while (Date.now() < holdUntil) {
|
|
1841
|
+
if (!latestMatrix.current || !condition(getYawDegrees(latestMatrix.current))) {
|
|
1842
|
+
holdBroken = true;
|
|
1843
|
+
break;
|
|
1844
|
+
}
|
|
1845
|
+
const holdStatus = checkFaceAlignment();
|
|
1846
|
+
if (!holdStatus.aligned) {
|
|
1847
|
+
setLiveFeedback({ message: holdStatus.message, isGood: false });
|
|
1848
|
+
holdBroken = true;
|
|
1849
|
+
break;
|
|
1850
|
+
}
|
|
1851
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1852
|
+
}
|
|
1853
|
+
if (!holdBroken && latestMatrix.current && condition(getYawDegrees(latestMatrix.current)) && checkFaceAlignment().aligned) {
|
|
1820
1854
|
return true;
|
|
1821
1855
|
}
|
|
1822
1856
|
}
|
|
@@ -1846,8 +1880,31 @@ var FaceZKInner = ({
|
|
|
1846
1880
|
setCurrentStep("capturing");
|
|
1847
1881
|
const video = videoRef.current;
|
|
1848
1882
|
let embedding;
|
|
1883
|
+
const occlusionCanvas = document.createElement("canvas");
|
|
1884
|
+
const checkFaceReadyAndOcclusion = (duringFlash = false) => {
|
|
1885
|
+
const alignment2 = checkFaceAlignment();
|
|
1886
|
+
if (!alignment2.aligned) return alignment2;
|
|
1887
|
+
if (occlusionCanvas.width !== video.videoWidth) {
|
|
1888
|
+
occlusionCanvas.width = video.videoWidth;
|
|
1889
|
+
occlusionCanvas.height = video.videoHeight;
|
|
1890
|
+
}
|
|
1891
|
+
const ctx = occlusionCanvas.getContext("2d", { willReadFrequently: true });
|
|
1892
|
+
if (!ctx) return { aligned: true, message: "Perfect! Hold still." };
|
|
1893
|
+
ctx.drawImage(video, 0, 0, occlusionCanvas.width, occlusionCanvas.height);
|
|
1894
|
+
const occlusion = checkFaceOcclusionRaw(
|
|
1895
|
+
latestLandmarks.current,
|
|
1896
|
+
ctx,
|
|
1897
|
+
occlusionCanvas.width,
|
|
1898
|
+
occlusionCanvas.height,
|
|
1899
|
+
{ yawDegrees: 0, duringLivenessFlash: duringFlash }
|
|
1900
|
+
);
|
|
1901
|
+
if (!occlusion.ok) {
|
|
1902
|
+
return { aligned: false, message: occlusion.message ?? "Please remove obstructions from your face." };
|
|
1903
|
+
}
|
|
1904
|
+
return { aligned: true, message: "Perfect! Hold still." };
|
|
1905
|
+
};
|
|
1849
1906
|
try {
|
|
1850
|
-
embedding = await captureMultipleEmbeddings(video, 10, 200);
|
|
1907
|
+
embedding = await captureMultipleEmbeddings(video, 10, 200, () => checkFaceReadyAndOcclusion(false));
|
|
1851
1908
|
} catch (e) {
|
|
1852
1909
|
handleFailure(e.message || "Failed to capture face data");
|
|
1853
1910
|
return;
|
|
@@ -1857,13 +1914,16 @@ var FaceZKInner = ({
|
|
|
1857
1914
|
handleFailure("Face lost during final check");
|
|
1858
1915
|
return;
|
|
1859
1916
|
}
|
|
1860
|
-
const alignment =
|
|
1917
|
+
const alignment = checkFaceReadyAndOcclusion(false);
|
|
1861
1918
|
if (!alignment.aligned) {
|
|
1862
1919
|
handleFailure(`Alignment lost: ${alignment.message}`);
|
|
1863
1920
|
return;
|
|
1864
1921
|
}
|
|
1865
1922
|
setCurrentStep("light-flash");
|
|
1866
|
-
const livenessResult = await livenessEngine.runLivenessSequence(
|
|
1923
|
+
const livenessResult = await livenessEngine.runLivenessSequence(
|
|
1924
|
+
bbox,
|
|
1925
|
+
() => checkFaceReadyAndOcclusion(true)
|
|
1926
|
+
);
|
|
1867
1927
|
if (!livenessResult.passed) {
|
|
1868
1928
|
handleFailure(livenessResult.message || "Liveness check failed");
|
|
1869
1929
|
return;
|
|
@@ -2963,6 +3023,7 @@ var VerifiedOnchainFlow_default = VerifiedOnchainFlow;
|
|
|
2963
3023
|
export {
|
|
2964
3024
|
BACKEND_PRIORITY,
|
|
2965
3025
|
CAPTURE_QUALITY,
|
|
3026
|
+
EMBEDDING_DIMENSIONS,
|
|
2966
3027
|
EmbeddingQuantizer,
|
|
2967
3028
|
FaceOcclusionTracker,
|
|
2968
3029
|
FaceZK_default as FaceZK,
|
|
@@ -2971,6 +3032,7 @@ export {
|
|
|
2971
3032
|
LivenessEngine,
|
|
2972
3033
|
MaskDetector,
|
|
2973
3034
|
ProofOfHumanity_default as ProofOfHumanity,
|
|
3035
|
+
SIGNATURE_BITS,
|
|
2974
3036
|
SocialConnection_default as SocialConnection,
|
|
2975
3037
|
VERIFICATION_CONSTANTS,
|
|
2976
3038
|
VerifiedOnchainFlow_default as VerifiedOnchainFlow,
|