@entros/pulse-sdk 2.0.0 → 3.1.0
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 -2
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +114 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +112 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@entros/pulse-sdk)
|
|
4
4
|
[](https://www.npmjs.com/package/@entros/pulse-sdk)
|
|
5
5
|
|
|
6
|
-
Client-side SDK for the Entros Protocol. Captures behavioral biometrics (voice, motion, touch), extracts a
|
|
6
|
+
Client-side SDK for the Entros Protocol. Captures behavioral biometrics (voice, motion, touch), extracts a 308-dimensional statistical feature vector (v3 expansion: MFCCs with pre-emphasis (C1-C12, MFCC[0] dropped), LPC coefficients, formant trajectories, voice quality, pitch contour shape, IMU FFT-band tremor, cross-axis covariance, mouse-derived FFT / autocorrelation analogues for desktop, touch curvature, gap distribution, path efficiency — see `docs/master/BLUEPRINT-feature-pipeline-v2.md`), generates a Groth16 zero-knowledge proof, and submits for on-chain verification on Solana. Raw biometric data stays on-device — only derived features and the proof are transmitted.
|
|
7
7
|
|
|
8
8
|
> **Looking for a drop-in?** Most integrators want [`@entros/verify`](https://github.com/entros-protocol/entros-verify) — a popup-pattern React component that wraps this SDK and ships verification in five lines of JSX. Use this package directly when you need to own the verification UX (custom capture canvas, branded loading states, mobile-native).
|
|
9
9
|
|
|
@@ -50,7 +50,7 @@ const result = await pulse.verify(touchElement);
|
|
|
50
50
|
## Pipeline
|
|
51
51
|
|
|
52
52
|
1. **Capture**: Audio (16kHz), IMU (accelerometer + gyroscope), touch (pressure + area) — event-driven, caller controls duration
|
|
53
|
-
2. **Extract**:
|
|
53
|
+
2. **Extract**: 308 features — speaker block (170): legacy F0 / jitter / shimmer / HNR / formant ratios / LTAS / amplitude (44) plus v3 additions: MFCCs + delta-MFCCs (72 — 12 used coefficients × 4 stats + 12 × 2 deltas, MFCC[0] dropped, pre-emphasis applied), LPC coefficient stats (24), formant absolute trajectories + bandwidths (16), voice quality CPP/tilt/H1-H2/sub-bands (9), pitch contour DCT (5). Motion block (81): legacy jerk + jounce per IMU axis (54) plus v2 additions: cross-axis covariance (6), FFT band energy on accel axes (12), physiological tremor peak (2), direction-reversal stats (3), motion-magnitude autocorrelation (4); desktop captures use mouse-derived analogues for these v2 additions. Touch block (57): legacy velocity + pressure dynamics (36) plus v2 additions: pressure derivative (4), contact aspect ratio + area derivative (4), trajectory curvature (3), velocity autocorrelation (3), inter-touch gap distribution (4), path efficiency + per-stroke length (3).
|
|
54
54
|
3. **Validate**: Feature summaries sent to Entros validation server for server-side analysis
|
|
55
55
|
4. **Hash**: SimHash → 256-bit Temporal Fingerprint → Poseidon commitment
|
|
56
56
|
5. **Prove**: Groth16 proof that new fingerprint is within Hamming distance of previous
|
package/dist/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ declare const DEFAULT_THRESHOLD = 96;
|
|
|
3
3
|
declare const DEFAULT_MIN_DISTANCE = 3;
|
|
4
4
|
declare const MIN_CAPTURE_MS = 2000;
|
|
5
5
|
declare const MAX_CAPTURE_MS = 60000;
|
|
6
|
-
declare const DEFAULT_CAPTURE_MS =
|
|
6
|
+
declare const DEFAULT_CAPTURE_MS = 12000;
|
|
7
7
|
declare const PROGRAM_IDS: {
|
|
8
8
|
readonly entrosAnchor: "GZYwTp2ozeuRA5Gof9vs4ya961aANcJBdUzB7LN6q4b2";
|
|
9
9
|
readonly entrosVerifier: "4F97jNoxQzT2qRbkWpW3ztC3Nz2TtKj3rnKG8ExgnrfV";
|
|
@@ -383,6 +383,8 @@ declare function extractSpeakerFeaturesDetailed(audio: AudioCapture): Promise<{
|
|
|
383
383
|
*/
|
|
384
384
|
declare function extractSpeakerFeatures(audio: AudioCapture): Promise<number[]>;
|
|
385
385
|
|
|
386
|
+
declare const MOTION_FEATURE_COUNT: number;
|
|
387
|
+
declare const TOUCH_FEATURE_COUNT: number;
|
|
386
388
|
/**
|
|
387
389
|
* Compute per-sample acceleration magnitude |a| = √(ax² + ay² + az²) and
|
|
388
390
|
* linearly resample to a target frame count. Surfaced for server-side
|
|
@@ -785,4 +787,4 @@ declare function fetchChallenge(executorUrl: string, walletAddress: string, apiK
|
|
|
785
787
|
*/
|
|
786
788
|
declare function encodeAudioAsBase64(samples: Float32Array): string;
|
|
787
789
|
|
|
788
|
-
export { type AgentHumanOperator, type AudioCapture, type CaptureOptions, type CaptureStage, type ChallengeResponse, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_MIN_DISTANCE, DEFAULT_THRESHOLD, type EntrosAttestation, FINGERPRINT_BITS, type FeatureVector, type FusedFeatureVector, type IdentityState, type LissajousParams, MAX_CAPTURE_MS, MIN_AUDIO_SAMPLES, MIN_CAPTURE_MS, MIN_MOTION_SAMPLES, MIN_TOUCH_SAMPLES, type MotionSample, PROGRAM_IDS, type PackedFingerprint, type Point2D, type ProofResult, type PulseConfig, PulseSDK, PulseSession, SPEAKER_FEATURE_COUNT, type SensorData, type SolanaProof, type StageState, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, type TemporalFingerprint, type TouchSample, type VerificationResult, attestAgentOperator, autocorrelation, bigintToBytes32, computeCommitment, condense, encodeAudioAsBase64, entropy, extractAccelerationMagnitude, extractMotionFeatures, extractMouseDynamics, extractSpeakerFeatures, extractSpeakerFeaturesDetailed, extractTouchFeatures, fetchChallenge, fetchIdentityState, fuseFeatures, fuseRawFeatures, generateLissajousPoints, generateLissajousSequence, generatePhrase, generatePhraseSequence, generateProof, generateSalt, generateSolanaProof, generateTBH, getAgentHumanOperator, hammingDistance, kurtosis, loadVerificationData, mean, packBits, prepareCircuitInput, randomLissajousParams, serializeProof, simhash, skewness, storeVerificationData, submitResetViaWallet, submitViaRelayer, submitViaWallet, toBigEndian32, variance, verifyEntrosAttestation };
|
|
790
|
+
export { type AgentHumanOperator, type AudioCapture, type CaptureOptions, type CaptureStage, type ChallengeResponse, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_MIN_DISTANCE, DEFAULT_THRESHOLD, type EntrosAttestation, FINGERPRINT_BITS, type FeatureVector, type FusedFeatureVector, type IdentityState, type LissajousParams, MAX_CAPTURE_MS, MIN_AUDIO_SAMPLES, MIN_CAPTURE_MS, MIN_MOTION_SAMPLES, MIN_TOUCH_SAMPLES, MOTION_FEATURE_COUNT, type MotionSample, PROGRAM_IDS, type PackedFingerprint, type Point2D, type ProofResult, type PulseConfig, PulseSDK, PulseSession, SPEAKER_FEATURE_COUNT, type SensorData, type SolanaProof, type StageState, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, TOUCH_FEATURE_COUNT, type TemporalFingerprint, type TouchSample, type VerificationResult, attestAgentOperator, autocorrelation, bigintToBytes32, computeCommitment, condense, encodeAudioAsBase64, entropy, extractAccelerationMagnitude, extractMotionFeatures, extractMouseDynamics, extractSpeakerFeatures, extractSpeakerFeaturesDetailed, extractTouchFeatures, fetchChallenge, fetchIdentityState, fuseFeatures, fuseRawFeatures, generateLissajousPoints, generateLissajousSequence, generatePhrase, generatePhraseSequence, generateProof, generateSalt, generateSolanaProof, generateTBH, getAgentHumanOperator, hammingDistance, kurtosis, loadVerificationData, mean, packBits, prepareCircuitInput, randomLissajousParams, serializeProof, simhash, skewness, storeVerificationData, submitResetViaWallet, submitViaRelayer, submitViaWallet, toBigEndian32, variance, verifyEntrosAttestation };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ declare const DEFAULT_THRESHOLD = 96;
|
|
|
3
3
|
declare const DEFAULT_MIN_DISTANCE = 3;
|
|
4
4
|
declare const MIN_CAPTURE_MS = 2000;
|
|
5
5
|
declare const MAX_CAPTURE_MS = 60000;
|
|
6
|
-
declare const DEFAULT_CAPTURE_MS =
|
|
6
|
+
declare const DEFAULT_CAPTURE_MS = 12000;
|
|
7
7
|
declare const PROGRAM_IDS: {
|
|
8
8
|
readonly entrosAnchor: "GZYwTp2ozeuRA5Gof9vs4ya961aANcJBdUzB7LN6q4b2";
|
|
9
9
|
readonly entrosVerifier: "4F97jNoxQzT2qRbkWpW3ztC3Nz2TtKj3rnKG8ExgnrfV";
|
|
@@ -383,6 +383,8 @@ declare function extractSpeakerFeaturesDetailed(audio: AudioCapture): Promise<{
|
|
|
383
383
|
*/
|
|
384
384
|
declare function extractSpeakerFeatures(audio: AudioCapture): Promise<number[]>;
|
|
385
385
|
|
|
386
|
+
declare const MOTION_FEATURE_COUNT: number;
|
|
387
|
+
declare const TOUCH_FEATURE_COUNT: number;
|
|
386
388
|
/**
|
|
387
389
|
* Compute per-sample acceleration magnitude |a| = √(ax² + ay² + az²) and
|
|
388
390
|
* linearly resample to a target frame count. Surfaced for server-side
|
|
@@ -785,4 +787,4 @@ declare function fetchChallenge(executorUrl: string, walletAddress: string, apiK
|
|
|
785
787
|
*/
|
|
786
788
|
declare function encodeAudioAsBase64(samples: Float32Array): string;
|
|
787
789
|
|
|
788
|
-
export { type AgentHumanOperator, type AudioCapture, type CaptureOptions, type CaptureStage, type ChallengeResponse, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_MIN_DISTANCE, DEFAULT_THRESHOLD, type EntrosAttestation, FINGERPRINT_BITS, type FeatureVector, type FusedFeatureVector, type IdentityState, type LissajousParams, MAX_CAPTURE_MS, MIN_AUDIO_SAMPLES, MIN_CAPTURE_MS, MIN_MOTION_SAMPLES, MIN_TOUCH_SAMPLES, type MotionSample, PROGRAM_IDS, type PackedFingerprint, type Point2D, type ProofResult, type PulseConfig, PulseSDK, PulseSession, SPEAKER_FEATURE_COUNT, type SensorData, type SolanaProof, type StageState, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, type TemporalFingerprint, type TouchSample, type VerificationResult, attestAgentOperator, autocorrelation, bigintToBytes32, computeCommitment, condense, encodeAudioAsBase64, entropy, extractAccelerationMagnitude, extractMotionFeatures, extractMouseDynamics, extractSpeakerFeatures, extractSpeakerFeaturesDetailed, extractTouchFeatures, fetchChallenge, fetchIdentityState, fuseFeatures, fuseRawFeatures, generateLissajousPoints, generateLissajousSequence, generatePhrase, generatePhraseSequence, generateProof, generateSalt, generateSolanaProof, generateTBH, getAgentHumanOperator, hammingDistance, kurtosis, loadVerificationData, mean, packBits, prepareCircuitInput, randomLissajousParams, serializeProof, simhash, skewness, storeVerificationData, submitResetViaWallet, submitViaRelayer, submitViaWallet, toBigEndian32, variance, verifyEntrosAttestation };
|
|
790
|
+
export { type AgentHumanOperator, type AudioCapture, type CaptureOptions, type CaptureStage, type ChallengeResponse, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_MIN_DISTANCE, DEFAULT_THRESHOLD, type EntrosAttestation, FINGERPRINT_BITS, type FeatureVector, type FusedFeatureVector, type IdentityState, type LissajousParams, MAX_CAPTURE_MS, MIN_AUDIO_SAMPLES, MIN_CAPTURE_MS, MIN_MOTION_SAMPLES, MIN_TOUCH_SAMPLES, MOTION_FEATURE_COUNT, type MotionSample, PROGRAM_IDS, type PackedFingerprint, type Point2D, type ProofResult, type PulseConfig, PulseSDK, PulseSession, SPEAKER_FEATURE_COUNT, type SensorData, type SolanaProof, type StageState, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, TOUCH_FEATURE_COUNT, type TemporalFingerprint, type TouchSample, type VerificationResult, attestAgentOperator, autocorrelation, bigintToBytes32, computeCommitment, condense, encodeAudioAsBase64, entropy, extractAccelerationMagnitude, extractMotionFeatures, extractMouseDynamics, extractSpeakerFeatures, extractSpeakerFeaturesDetailed, extractTouchFeatures, fetchChallenge, fetchIdentityState, fuseFeatures, fuseRawFeatures, generateLissajousPoints, generateLissajousSequence, generatePhrase, generatePhraseSequence, generateProof, generateSalt, generateSolanaProof, generateTBH, getAgentHumanOperator, hammingDistance, kurtosis, loadVerificationData, mean, packBits, prepareCircuitInput, randomLissajousParams, serializeProof, simhash, skewness, storeVerificationData, submitResetViaWallet, submitViaRelayer, submitViaWallet, toBigEndian32, variance, verifyEntrosAttestation };
|
package/dist/index.js
CHANGED
|
@@ -39,10 +39,12 @@ __export(index_exports, {
|
|
|
39
39
|
MIN_CAPTURE_MS: () => MIN_CAPTURE_MS,
|
|
40
40
|
MIN_MOTION_SAMPLES: () => MIN_MOTION_SAMPLES,
|
|
41
41
|
MIN_TOUCH_SAMPLES: () => MIN_TOUCH_SAMPLES,
|
|
42
|
+
MOTION_FEATURE_COUNT: () => MOTION_FEATURE_COUNT,
|
|
42
43
|
PROGRAM_IDS: () => PROGRAM_IDS,
|
|
43
44
|
PulseSDK: () => PulseSDK,
|
|
44
45
|
PulseSession: () => PulseSession,
|
|
45
46
|
SPEAKER_FEATURE_COUNT: () => SPEAKER_FEATURE_COUNT,
|
|
47
|
+
TOUCH_FEATURE_COUNT: () => TOUCH_FEATURE_COUNT,
|
|
46
48
|
attestAgentOperator: () => attestAgentOperator,
|
|
47
49
|
autocorrelation: () => autocorrelation,
|
|
48
50
|
bigintToBytes32: () => bigintToBytes32,
|
|
@@ -107,7 +109,7 @@ var TOTAL_PROOF_SIZE = 256;
|
|
|
107
109
|
var SIMHASH_SEED = "IAM-PROTOCOL-SIMHASH-V1";
|
|
108
110
|
var MIN_CAPTURE_MS = 2e3;
|
|
109
111
|
var MAX_CAPTURE_MS = 6e4;
|
|
110
|
-
var DEFAULT_CAPTURE_MS =
|
|
112
|
+
var DEFAULT_CAPTURE_MS = 12e3;
|
|
111
113
|
var PROGRAM_IDS = {
|
|
112
114
|
entrosAnchor: "GZYwTp2ozeuRA5Gof9vs4ya961aANcJBdUzB7LN6q4b2",
|
|
113
115
|
entrosVerifier: "4F97jNoxQzT2qRbkWpW3ztC3Nz2TtKj3rnKG8ExgnrfV",
|
|
@@ -138,6 +140,25 @@ function sdkWarn(...args) {
|
|
|
138
140
|
|
|
139
141
|
// src/sensor/audio.ts
|
|
140
142
|
var TARGET_SAMPLE_RATE = 16e3;
|
|
143
|
+
var TARGET_CAPTURE_RMS = 0.05;
|
|
144
|
+
var MIN_RMS_FOR_NORMALIZATION = 1e-4;
|
|
145
|
+
var MAX_NORMALIZATION_GAIN = 50;
|
|
146
|
+
function normalizeCaptureRMS(samples) {
|
|
147
|
+
if (samples.length === 0) return samples;
|
|
148
|
+
let sumSq = 0;
|
|
149
|
+
for (let i = 0; i < samples.length; i++) {
|
|
150
|
+
const s = samples[i];
|
|
151
|
+
sumSq += s * s;
|
|
152
|
+
}
|
|
153
|
+
const rms = Math.sqrt(sumSq / samples.length);
|
|
154
|
+
if (rms < MIN_RMS_FOR_NORMALIZATION) return samples;
|
|
155
|
+
const gain = Math.min(TARGET_CAPTURE_RMS / rms, MAX_NORMALIZATION_GAIN);
|
|
156
|
+
const out = new Float32Array(samples.length);
|
|
157
|
+
for (let i = 0; i < samples.length; i++) {
|
|
158
|
+
out[i] = Math.max(-1, Math.min(1, samples[i] * gain));
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
141
162
|
async function captureAudio(options = {}) {
|
|
142
163
|
const {
|
|
143
164
|
signal,
|
|
@@ -225,8 +246,9 @@ async function captureAudio(options = {}) {
|
|
|
225
246
|
samples.set(chunk, offset);
|
|
226
247
|
offset += chunk.length;
|
|
227
248
|
}
|
|
249
|
+
const normalized = normalizeCaptureRMS(samples);
|
|
228
250
|
resolve({
|
|
229
|
-
samples,
|
|
251
|
+
samples: normalized,
|
|
230
252
|
sampleRate: capturedSampleRate,
|
|
231
253
|
duration: totalLength / capturedSampleRate
|
|
232
254
|
});
|
|
@@ -401,6 +423,9 @@ function variance(values, mu) {
|
|
|
401
423
|
for (const v of values) sum += (v - m) ** 2;
|
|
402
424
|
return sum / (values.length - 1);
|
|
403
425
|
}
|
|
426
|
+
var SKEWNESS_BOUND = 20;
|
|
427
|
+
var KURTOSIS_LOWER = 0;
|
|
428
|
+
var KURTOSIS_UPPER = 50;
|
|
404
429
|
function skewness(values) {
|
|
405
430
|
if (values.length < 3) return 0;
|
|
406
431
|
const n = values.length;
|
|
@@ -409,7 +434,8 @@ function skewness(values) {
|
|
|
409
434
|
if (s === 0) return 0;
|
|
410
435
|
let sum = 0;
|
|
411
436
|
for (const v of values) sum += ((v - m) / s) ** 3;
|
|
412
|
-
|
|
437
|
+
const raw = n / ((n - 1) * (n - 2)) * sum;
|
|
438
|
+
return Math.max(-SKEWNESS_BOUND, Math.min(SKEWNESS_BOUND, raw));
|
|
413
439
|
}
|
|
414
440
|
function kurtosis(values) {
|
|
415
441
|
if (values.length < 4) return 0;
|
|
@@ -420,7 +446,7 @@ function kurtosis(values) {
|
|
|
420
446
|
let sum = 0;
|
|
421
447
|
for (const v of values) sum += (v - m) ** 4 / s2 ** 2;
|
|
422
448
|
const k = n * (n + 1) / ((n - 1) * (n - 2) * (n - 3)) * sum - 3 * (n - 1) ** 2 / ((n - 2) * (n - 3));
|
|
423
|
-
return k;
|
|
449
|
+
return Math.max(KURTOSIS_LOWER, Math.min(KURTOSIS_UPPER, k));
|
|
424
450
|
}
|
|
425
451
|
function condense(values) {
|
|
426
452
|
const m = mean(values);
|
|
@@ -681,9 +707,20 @@ function extractLpcAnalysis(samples, sampleRate, frameSize, hopSize, lpcOrder =
|
|
|
681
707
|
|
|
682
708
|
// src/extraction/mfcc.ts
|
|
683
709
|
var NUM_MFCC_COEFFICIENTS = 13;
|
|
710
|
+
var MFCC_DROP_LEADING = 1;
|
|
711
|
+
var NUM_USED_MFCC = NUM_MFCC_COEFFICIENTS - MFCC_DROP_LEADING;
|
|
684
712
|
var DELTA_REGRESSION_HALF_WIDTH = 2;
|
|
685
|
-
var MFCC_FEATURE_COUNT =
|
|
686
|
-
|
|
713
|
+
var MFCC_FEATURE_COUNT = NUM_USED_MFCC * 4 + // mean, var, skew, kurt per coefficient
|
|
714
|
+
NUM_USED_MFCC * 2;
|
|
715
|
+
function applyPreEmphasis(samples) {
|
|
716
|
+
const out = new Float32Array(samples.length);
|
|
717
|
+
if (samples.length === 0) return out;
|
|
718
|
+
out[0] = samples[0];
|
|
719
|
+
for (let i = 1; i < samples.length; i++) {
|
|
720
|
+
out[i] = samples[i] - 0.97 * samples[i - 1];
|
|
721
|
+
}
|
|
722
|
+
return out;
|
|
723
|
+
}
|
|
687
724
|
function computeDelta(series, halfWidth) {
|
|
688
725
|
const n = series.length;
|
|
689
726
|
const out = new Array(n);
|
|
@@ -733,15 +770,16 @@ async function extractMfccFeatures(samples, sampleRate, frameSize, hopSize) {
|
|
|
733
770
|
return new Array(MFCC_FEATURE_COUNT).fill(0);
|
|
734
771
|
}
|
|
735
772
|
const mfccTracks = Array.from(
|
|
736
|
-
{ length:
|
|
773
|
+
{ length: NUM_USED_MFCC },
|
|
737
774
|
() => []
|
|
738
775
|
);
|
|
739
776
|
const frame = new Float32Array(frameSize);
|
|
740
777
|
Meyda.bufferSize = frameSize;
|
|
741
778
|
Meyda.sampleRate = sampleRate;
|
|
779
|
+
const emphasized = applyPreEmphasis(samples);
|
|
742
780
|
for (let i = 0; i < numFrames; i++) {
|
|
743
781
|
const start = i * hopSize;
|
|
744
|
-
frame.set(
|
|
782
|
+
frame.set(emphasized.subarray(start, start + frameSize), 0);
|
|
745
783
|
const result = Meyda.extract("mfcc", frame);
|
|
746
784
|
if (!Array.isArray(result) || result.length !== NUM_MFCC_COEFFICIENTS) {
|
|
747
785
|
continue;
|
|
@@ -754,21 +792,21 @@ async function extractMfccFeatures(samples, sampleRate, frameSize, hopSize) {
|
|
|
754
792
|
}
|
|
755
793
|
}
|
|
756
794
|
if (!allFinite) continue;
|
|
757
|
-
for (let c = 0; c <
|
|
758
|
-
mfccTracks[c].push(result[c]);
|
|
795
|
+
for (let c = 0; c < NUM_USED_MFCC; c++) {
|
|
796
|
+
mfccTracks[c].push(result[c + MFCC_DROP_LEADING]);
|
|
759
797
|
}
|
|
760
798
|
}
|
|
761
799
|
const out = [];
|
|
762
800
|
out.length = MFCC_FEATURE_COUNT;
|
|
763
801
|
let writeIdx = 0;
|
|
764
|
-
for (let c = 0; c <
|
|
802
|
+
for (let c = 0; c < NUM_USED_MFCC; c++) {
|
|
765
803
|
const stats = condense(mfccTracks[c]);
|
|
766
804
|
out[writeIdx++] = stats.mean;
|
|
767
805
|
out[writeIdx++] = stats.variance;
|
|
768
806
|
out[writeIdx++] = stats.skewness;
|
|
769
807
|
out[writeIdx++] = stats.kurtosis;
|
|
770
808
|
}
|
|
771
|
-
for (let c = 0; c <
|
|
809
|
+
for (let c = 0; c < NUM_USED_MFCC; c++) {
|
|
772
810
|
const delta = computeDelta(mfccTracks[c], DELTA_REGRESSION_HALF_WIDTH);
|
|
773
811
|
const muDelta = mean(delta);
|
|
774
812
|
out[writeIdx++] = muDelta;
|
|
@@ -1287,9 +1325,9 @@ async function extractSpeakerFeaturesDetailed(audio) {
|
|
|
1287
1325
|
for (let i = 0; i < numFrames; i++) {
|
|
1288
1326
|
const start = i * hopSize;
|
|
1289
1327
|
let sum = 0;
|
|
1290
|
-
const end = Math.min(start + frameSize,
|
|
1328
|
+
const end = Math.min(start + frameSize, normalizedSamples.length);
|
|
1291
1329
|
for (let j = start; j < end; j++) {
|
|
1292
|
-
sum += (
|
|
1330
|
+
sum += (normalizedSamples[j] ?? 0) * (normalizedSamples[j] ?? 0);
|
|
1293
1331
|
}
|
|
1294
1332
|
amplitudes.push(Math.sqrt(sum / (end - start)));
|
|
1295
1333
|
}
|
|
@@ -1398,15 +1436,15 @@ async function extractSpeakerFeaturesDetailed(audio) {
|
|
|
1398
1436
|
...ampFeatures,
|
|
1399
1437
|
// 5 [39..44] AMPLITUDE
|
|
1400
1438
|
...mfccFeatures,
|
|
1401
|
-
//
|
|
1439
|
+
// 72 [44..116] MFCC + delta-MFCC (MFCC[0] dropped)
|
|
1402
1440
|
...lpcStats,
|
|
1403
|
-
// 24 [
|
|
1441
|
+
// 24 [116..140] LPC coefficient stats
|
|
1404
1442
|
...formantTrajectoryFeatures,
|
|
1405
|
-
// 16 [
|
|
1443
|
+
// 16 [140..156] Formant absolutes + dynamics + bandwidths
|
|
1406
1444
|
...voiceQualityFeatures,
|
|
1407
|
-
// 9 [
|
|
1445
|
+
// 9 [156..165] Voice quality
|
|
1408
1446
|
...pitchShapeFeatures
|
|
1409
|
-
// 5 [
|
|
1447
|
+
// 5 [165..170] Pitch contour shape DCT
|
|
1410
1448
|
];
|
|
1411
1449
|
return { features, f0Contour: f0 };
|
|
1412
1450
|
}
|
|
@@ -1932,8 +1970,61 @@ function extractMouseDynamics(samples) {
|
|
|
1932
1970
|
angleAutoCorr[2] ?? 0,
|
|
1933
1971
|
normalizedPathLength
|
|
1934
1972
|
];
|
|
1935
|
-
const
|
|
1936
|
-
return
|
|
1973
|
+
const v2 = computeMouseV2(samples, vx, vy, accX, accY, speed, acc, jerk, directions);
|
|
1974
|
+
return [...legacyMouseDynamics, ...v2];
|
|
1975
|
+
}
|
|
1976
|
+
function computeMouseV2(samples, vx, vy, accX, accY, speed, acc, jerk, directions) {
|
|
1977
|
+
const out = [];
|
|
1978
|
+
const covPairs = [
|
|
1979
|
+
[vx, vy],
|
|
1980
|
+
[vx, accX],
|
|
1981
|
+
[vx, accY],
|
|
1982
|
+
[vy, accX],
|
|
1983
|
+
[vy, accY],
|
|
1984
|
+
[accX, accY]
|
|
1985
|
+
];
|
|
1986
|
+
for (const [a, b] of covPairs) out.push(covariance(a, b));
|
|
1987
|
+
const sampleRate = sampleRateFromTimestamps(samples.map((s) => s.timestamp));
|
|
1988
|
+
const fftSize = nextPow2(Math.max(64, speed.length));
|
|
1989
|
+
const bands = [
|
|
1990
|
+
[0, 2],
|
|
1991
|
+
[2, 6],
|
|
1992
|
+
[6, 12],
|
|
1993
|
+
[12, 30]
|
|
1994
|
+
];
|
|
1995
|
+
const speedSpectrum = realFFT(meanCenter(speed), fftSize);
|
|
1996
|
+
const accSpectrum = realFFT(meanCenter(acc), fftSize);
|
|
1997
|
+
const jerkSpectrum = realFFT(meanCenter(jerk), fftSize);
|
|
1998
|
+
for (const spectrum of [speedSpectrum, accSpectrum, jerkSpectrum]) {
|
|
1999
|
+
for (const [lo, hi] of bands) {
|
|
2000
|
+
out.push(bandEnergy(spectrum.real, spectrum.imag, sampleRate, lo, hi));
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
const tremor = peakInBand(
|
|
2004
|
+
speedSpectrum.real,
|
|
2005
|
+
speedSpectrum.imag,
|
|
2006
|
+
sampleRate,
|
|
2007
|
+
4,
|
|
2008
|
+
12
|
|
2009
|
+
);
|
|
2010
|
+
out.push(tremor.freq, tremor.amplitude);
|
|
2011
|
+
const duration = captureDurationSec(samples);
|
|
2012
|
+
const reversalRates = [vx, vy, speed].map(
|
|
2013
|
+
(channel) => duration > 0 ? signChangeCount(derivative2(channel)) / duration : 0
|
|
2014
|
+
);
|
|
2015
|
+
out.push(mean(reversalRates), variance(reversalRates));
|
|
2016
|
+
let dirAccum = 0;
|
|
2017
|
+
for (let i = 1; i < directions.length; i++) {
|
|
2018
|
+
let diff = directions[i] - directions[i - 1];
|
|
2019
|
+
while (diff > Math.PI) diff -= 2 * Math.PI;
|
|
2020
|
+
while (diff < -Math.PI) diff += 2 * Math.PI;
|
|
2021
|
+
dirAccum += Math.abs(diff);
|
|
2022
|
+
}
|
|
2023
|
+
out.push(directions.length > 1 ? dirAccum / (directions.length - 1) : 0);
|
|
2024
|
+
for (const lag of [1, 5, 10, 25]) {
|
|
2025
|
+
out.push(autocorrelation(speed, lag));
|
|
2026
|
+
}
|
|
2027
|
+
return out.map((v) => Number.isFinite(v) ? v : 0);
|
|
1937
2028
|
}
|
|
1938
2029
|
|
|
1939
2030
|
// src/hashing/simhash.ts
|
|
@@ -6344,10 +6435,12 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
|
|
|
6344
6435
|
MIN_CAPTURE_MS,
|
|
6345
6436
|
MIN_MOTION_SAMPLES,
|
|
6346
6437
|
MIN_TOUCH_SAMPLES,
|
|
6438
|
+
MOTION_FEATURE_COUNT,
|
|
6347
6439
|
PROGRAM_IDS,
|
|
6348
6440
|
PulseSDK,
|
|
6349
6441
|
PulseSession,
|
|
6350
6442
|
SPEAKER_FEATURE_COUNT,
|
|
6443
|
+
TOUCH_FEATURE_COUNT,
|
|
6351
6444
|
attestAgentOperator,
|
|
6352
6445
|
autocorrelation,
|
|
6353
6446
|
bigintToBytes32,
|