@verifiedonchain-protocol/sdk 0.1.0 → 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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # VerifiedOnchain SDK (`vo-sdk`)
1
+ # VerifiedOnchain SDK (`@verifiedonchain-protocol/sdk`)
2
2
 
3
3
  A lightweight, framework-agnostic React SDK for integrating the **Proof of Humanity** face verification, social connections, and wallet abstractions into your dApp.
4
4
 
@@ -7,7 +7,7 @@ This SDK captures biometric telemetry, performs local sequential liveness detect
7
7
  ## Installation
8
8
 
9
9
  ```bash
10
- npm install vo-sdk
10
+ npm install @verifiedonchain-protocol/sdk
11
11
  ```
12
12
 
13
13
  *(Note: If you are using React 18, you might need to install peer dependencies like `framer-motion` for animations, `@solana/wallet-adapter-react` for Solana support, and `wagmi` + `viem` for EVM support if you haven't already).*
@@ -20,15 +20,15 @@ The SDK requires two things to function correctly:
20
20
 
21
21
  ### 1. Hosting the ML Models
22
22
 
23
- Download the standard Human.js models (or copy them from the SDK's `node_modules/vo-sdk/models` if available) and place them in your web root under `/models`. The required models typically include `blazeface.json`, `faceres.json`, etc.
23
+ Download the standard Human.js models (or copy them from the SDK's `node_modules/@verifiedonchain-protocol/sdk/models` if available) and place them in your web root under `/models`. The required models typically include `blazeface.json`, `faceres.json`, etc.
24
24
 
25
25
  ### 2. Basic Usage
26
26
 
27
27
  The easiest way to integrate the SDK is using the `VerifiedOnchainFlow` component, which provides a complete turn-key UI for wallet connection, social linking, and biometric scanning.
28
28
 
29
29
  ```tsx
30
- import { VerifiedOnchainFlow } from 'vo-sdk';
31
- import 'vo-sdk/dist/index.css'; // Optional if you are compiling Tailwind yourself
30
+ import { VerifiedOnchainFlow } from '@verifiedonchain-protocol/sdk';
31
+ import '@verifiedonchain-protocol/sdk/dist/index.css'; // Optional if you are compiling Tailwind yourself
32
32
 
33
33
  export default function VerificationPage() {
34
34
  const handleSuccess = (data) => {
@@ -79,7 +79,7 @@ If you need complete control over the user experience, styling, or the order of
79
79
  A plug-and-play UI for wallet connections across both EVM and Solana ecosystems.
80
80
 
81
81
  ```tsx
82
- import { WalletSelection } from 'vo-sdk';
82
+ import { WalletSelection } from '@verifiedonchain-protocol/sdk';
83
83
 
84
84
  <WalletSelection
85
85
  requiredWallets={['phantom', 'metamask', 'backpack']}
@@ -93,7 +93,7 @@ import { WalletSelection } from 'vo-sdk';
93
93
  Handles OAuth flows and API integrations for social verification.
94
94
 
95
95
  ```tsx
96
- import { SocialConnection } from 'vo-sdk';
96
+ import { SocialConnection } from '@verifiedonchain-protocol/sdk';
97
97
 
98
98
  <SocialConnection
99
99
  requiredSocials={['x', 'farcaster']}
@@ -107,7 +107,7 @@ import { SocialConnection } from 'vo-sdk';
107
107
  The core biometric scanners. `ProofOfHumanity` provides a structured UI wrapper around the raw `FaceZK` scanner.
108
108
 
109
109
  ```tsx
110
- import { ProofOfHumanity } from 'vo-sdk';
110
+ import { ProofOfHumanity } from '@verifiedonchain-protocol/sdk';
111
111
 
112
112
  <ProofOfHumanity
113
113
  onNextStep={() => console.log("User clicked continue")}
@@ -122,8 +122,8 @@ import { ProofOfHumanity } from 'vo-sdk';
122
122
 
123
123
  If you are using the **Headless / Modular Approach**, you are responsible for submitting the biometric payload to the backend using the `VerifiedRelayerClient`.
124
124
 
125
- ```tsx
126
- import { VerifiedRelayerClient } from 'vo-sdk';
125
+ ```typescript
126
+ import { VerifiedRelayerClient } from '@verifiedonchain-protocol/sdk';
127
127
 
128
128
  // Initialize the client
129
129
  const client = new VerifiedRelayerClient({
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 dims = Math.min(embedding.length, hyperplanes[0].length);
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 += embedding[j] * hyperplanes[i][j];
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 = 1024, count = 256) {
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.7));
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(1024, 256);
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
- await new Promise((r) => setTimeout(r, 300));
1819
- if (latestMatrix.current && condition(getYawDegrees(latestMatrix.current))) {
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 = checkFaceAlignment();
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(bbox);
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,