@iam-protocol/pulse-sdk 0.1.1 → 0.2.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/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  declare const FINGERPRINT_BITS = 256;
2
2
  declare const DEFAULT_THRESHOLD = 30;
3
+ declare const DEFAULT_MIN_DISTANCE = 3;
3
4
  declare const MIN_CAPTURE_MS = 2000;
4
5
  declare const MAX_CAPTURE_MS = 60000;
5
6
  declare const DEFAULT_CAPTURE_MS = 7000;
@@ -50,8 +51,6 @@ interface CaptureOptions {
50
51
  minDurationMs?: number;
51
52
  /** Maximum capture duration in ms. Auto-stops if signal hasn't fired. Default: 60000 */
52
53
  maxDurationMs?: number;
53
- /** Called with RMS audio level (0-1) on each buffer during audio capture (~4x per second). */
54
- onAudioLevel?: (rms: number) => void;
55
54
  }
56
55
  /** Stage of a capture session */
57
56
  type CaptureStage = "audio" | "motion" | "touch";
@@ -120,7 +119,7 @@ declare class PulseSession {
120
119
  private motionData;
121
120
  private touchData;
122
121
  constructor(config: ResolvedConfig, touchElement?: HTMLElement);
123
- startAudio(onAudioLevel?: (rms: number) => void): Promise<void>;
122
+ startAudio(): Promise<void>;
124
123
  stopAudio(): Promise<AudioCapture | null>;
125
124
  skipAudio(): void;
126
125
  startMotion(): Promise<void>;
@@ -224,6 +223,18 @@ declare function variance(values: number[], mu?: number): number;
224
223
  declare function skewness(values: number[]): number;
225
224
  declare function kurtosis(values: number[]): number;
226
225
  declare function condense(values: number[]): StatsSummary;
226
+ /**
227
+ * Shannon entropy over histogram bins. Measures information density.
228
+ * Real human data has moderate entropy (varied but structured).
229
+ * Synthetic data is either too uniform (high entropy) or too structured (low entropy).
230
+ */
231
+ declare function entropy(values: number[], bins?: number): number;
232
+ /**
233
+ * Autocorrelation at a given lag. Detects periodic synthetic patterns.
234
+ * Real human data has low autocorrelation at most lags (chaotic/noisy).
235
+ * Synthetic data often has high autocorrelation (periodic/smooth).
236
+ */
237
+ declare function autocorrelation(values: number[], lag?: number): number;
227
238
  declare function fuseFeatures(audio: number[], motion: number[], touch: number[]): number[];
228
239
 
229
240
  /** Serialized proof ready for on-chain submission */
@@ -248,6 +259,7 @@ interface CircuitInput {
248
259
  commitment_new: string;
249
260
  commitment_prev: string;
250
261
  threshold: string;
262
+ min_distance: string;
251
263
  }
252
264
  /** Proof generation result */
253
265
  interface ProofResult {
@@ -271,7 +283,7 @@ declare function serializeProof(proof: RawProof, publicSignals: string[]): Solan
271
283
  /**
272
284
  * Prepare circuit input from current and previous TBH data.
273
285
  */
274
- declare function prepareCircuitInput(current: TBH, previous: TBH, threshold?: number): CircuitInput;
286
+ declare function prepareCircuitInput(current: TBH, previous: TBH, threshold?: number, minDistance?: number): CircuitInput;
275
287
  /**
276
288
  * Generate a Groth16 proof for the Hamming distance circuit.
277
289
  *
@@ -375,4 +387,4 @@ declare function randomLissajousParams(): LissajousParams;
375
387
  */
376
388
  declare function generateLissajousPoints(params: LissajousParams): Point2D[];
377
389
 
378
- export { type AudioCapture, type CaptureOptions, type CaptureStage, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_THRESHOLD, FINGERPRINT_BITS, type FeatureVector, type FusedFeatureVector, type IdentityState, type LissajousParams, MAX_CAPTURE_MS, MIN_CAPTURE_MS, type MotionSample, PROGRAM_IDS, type PackedFingerprint, type Point2D, type ProofResult, type PulseConfig, PulseSDK, PulseSession, type SensorData, type SolanaProof, type StageState, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, type TemporalFingerprint, type TouchSample, type VerificationResult, bigintToBytes32, computeCommitment, condense, fetchIdentityState, fuseFeatures, generateLissajousPoints, generatePhrase, generateProof, generateSalt, generateSolanaProof, generateTBH, hammingDistance, kurtosis, loadVerificationData, mean, packBits, prepareCircuitInput, randomLissajousParams, serializeProof, simhash, skewness, storeVerificationData, submitViaRelayer, submitViaWallet, toBigEndian32, variance };
390
+ export { type AudioCapture, type CaptureOptions, type CaptureStage, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_MIN_DISTANCE, DEFAULT_THRESHOLD, FINGERPRINT_BITS, type FeatureVector, type FusedFeatureVector, type IdentityState, type LissajousParams, MAX_CAPTURE_MS, MIN_CAPTURE_MS, type MotionSample, PROGRAM_IDS, type PackedFingerprint, type Point2D, type ProofResult, type PulseConfig, PulseSDK, PulseSession, type SensorData, type SolanaProof, type StageState, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, type TemporalFingerprint, type TouchSample, type VerificationResult, autocorrelation, bigintToBytes32, computeCommitment, condense, entropy, fetchIdentityState, fuseFeatures, generateLissajousPoints, generatePhrase, generateProof, generateSalt, generateSolanaProof, generateTBH, hammingDistance, kurtosis, loadVerificationData, mean, packBits, prepareCircuitInput, randomLissajousParams, serializeProof, simhash, skewness, storeVerificationData, submitViaRelayer, submitViaWallet, toBigEndian32, variance };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  declare const FINGERPRINT_BITS = 256;
2
2
  declare const DEFAULT_THRESHOLD = 30;
3
+ declare const DEFAULT_MIN_DISTANCE = 3;
3
4
  declare const MIN_CAPTURE_MS = 2000;
4
5
  declare const MAX_CAPTURE_MS = 60000;
5
6
  declare const DEFAULT_CAPTURE_MS = 7000;
@@ -50,8 +51,6 @@ interface CaptureOptions {
50
51
  minDurationMs?: number;
51
52
  /** Maximum capture duration in ms. Auto-stops if signal hasn't fired. Default: 60000 */
52
53
  maxDurationMs?: number;
53
- /** Called with RMS audio level (0-1) on each buffer during audio capture (~4x per second). */
54
- onAudioLevel?: (rms: number) => void;
55
54
  }
56
55
  /** Stage of a capture session */
57
56
  type CaptureStage = "audio" | "motion" | "touch";
@@ -120,7 +119,7 @@ declare class PulseSession {
120
119
  private motionData;
121
120
  private touchData;
122
121
  constructor(config: ResolvedConfig, touchElement?: HTMLElement);
123
- startAudio(onAudioLevel?: (rms: number) => void): Promise<void>;
122
+ startAudio(): Promise<void>;
124
123
  stopAudio(): Promise<AudioCapture | null>;
125
124
  skipAudio(): void;
126
125
  startMotion(): Promise<void>;
@@ -224,6 +223,18 @@ declare function variance(values: number[], mu?: number): number;
224
223
  declare function skewness(values: number[]): number;
225
224
  declare function kurtosis(values: number[]): number;
226
225
  declare function condense(values: number[]): StatsSummary;
226
+ /**
227
+ * Shannon entropy over histogram bins. Measures information density.
228
+ * Real human data has moderate entropy (varied but structured).
229
+ * Synthetic data is either too uniform (high entropy) or too structured (low entropy).
230
+ */
231
+ declare function entropy(values: number[], bins?: number): number;
232
+ /**
233
+ * Autocorrelation at a given lag. Detects periodic synthetic patterns.
234
+ * Real human data has low autocorrelation at most lags (chaotic/noisy).
235
+ * Synthetic data often has high autocorrelation (periodic/smooth).
236
+ */
237
+ declare function autocorrelation(values: number[], lag?: number): number;
227
238
  declare function fuseFeatures(audio: number[], motion: number[], touch: number[]): number[];
228
239
 
229
240
  /** Serialized proof ready for on-chain submission */
@@ -248,6 +259,7 @@ interface CircuitInput {
248
259
  commitment_new: string;
249
260
  commitment_prev: string;
250
261
  threshold: string;
262
+ min_distance: string;
251
263
  }
252
264
  /** Proof generation result */
253
265
  interface ProofResult {
@@ -271,7 +283,7 @@ declare function serializeProof(proof: RawProof, publicSignals: string[]): Solan
271
283
  /**
272
284
  * Prepare circuit input from current and previous TBH data.
273
285
  */
274
- declare function prepareCircuitInput(current: TBH, previous: TBH, threshold?: number): CircuitInput;
286
+ declare function prepareCircuitInput(current: TBH, previous: TBH, threshold?: number, minDistance?: number): CircuitInput;
275
287
  /**
276
288
  * Generate a Groth16 proof for the Hamming distance circuit.
277
289
  *
@@ -375,4 +387,4 @@ declare function randomLissajousParams(): LissajousParams;
375
387
  */
376
388
  declare function generateLissajousPoints(params: LissajousParams): Point2D[];
377
389
 
378
- export { type AudioCapture, type CaptureOptions, type CaptureStage, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_THRESHOLD, FINGERPRINT_BITS, type FeatureVector, type FusedFeatureVector, type IdentityState, type LissajousParams, MAX_CAPTURE_MS, MIN_CAPTURE_MS, type MotionSample, PROGRAM_IDS, type PackedFingerprint, type Point2D, type ProofResult, type PulseConfig, PulseSDK, PulseSession, type SensorData, type SolanaProof, type StageState, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, type TemporalFingerprint, type TouchSample, type VerificationResult, bigintToBytes32, computeCommitment, condense, fetchIdentityState, fuseFeatures, generateLissajousPoints, generatePhrase, generateProof, generateSalt, generateSolanaProof, generateTBH, hammingDistance, kurtosis, loadVerificationData, mean, packBits, prepareCircuitInput, randomLissajousParams, serializeProof, simhash, skewness, storeVerificationData, submitViaRelayer, submitViaWallet, toBigEndian32, variance };
390
+ export { type AudioCapture, type CaptureOptions, type CaptureStage, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_MIN_DISTANCE, DEFAULT_THRESHOLD, FINGERPRINT_BITS, type FeatureVector, type FusedFeatureVector, type IdentityState, type LissajousParams, MAX_CAPTURE_MS, MIN_CAPTURE_MS, type MotionSample, PROGRAM_IDS, type PackedFingerprint, type Point2D, type ProofResult, type PulseConfig, PulseSDK, PulseSession, type SensorData, type SolanaProof, type StageState, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, type TemporalFingerprint, type TouchSample, type VerificationResult, autocorrelation, bigintToBytes32, computeCommitment, condense, entropy, fetchIdentityState, fuseFeatures, generateLissajousPoints, generatePhrase, generateProof, generateSalt, generateSolanaProof, generateTBH, hammingDistance, kurtosis, loadVerificationData, mean, packBits, prepareCircuitInput, randomLissajousParams, serializeProof, simhash, skewness, storeVerificationData, submitViaRelayer, submitViaWallet, toBigEndian32, variance };
package/dist/index.js CHANGED
@@ -12978,6 +12978,7 @@ var init_esm4 = __esm({
12978
12978
  var index_exports = {};
12979
12979
  __export(index_exports, {
12980
12980
  DEFAULT_CAPTURE_MS: () => DEFAULT_CAPTURE_MS,
12981
+ DEFAULT_MIN_DISTANCE: () => DEFAULT_MIN_DISTANCE,
12981
12982
  DEFAULT_THRESHOLD: () => DEFAULT_THRESHOLD,
12982
12983
  FINGERPRINT_BITS: () => FINGERPRINT_BITS,
12983
12984
  MAX_CAPTURE_MS: () => MAX_CAPTURE_MS,
@@ -12985,9 +12986,11 @@ __export(index_exports, {
12985
12986
  PROGRAM_IDS: () => PROGRAM_IDS,
12986
12987
  PulseSDK: () => PulseSDK,
12987
12988
  PulseSession: () => PulseSession,
12989
+ autocorrelation: () => autocorrelation,
12988
12990
  bigintToBytes32: () => bigintToBytes32,
12989
12991
  computeCommitment: () => computeCommitment,
12990
12992
  condense: () => condense,
12993
+ entropy: () => entropy,
12991
12994
  fetchIdentityState: () => fetchIdentityState,
12992
12995
  fuseFeatures: () => fuseFeatures,
12993
12996
  generateLissajousPoints: () => generateLissajousPoints,
@@ -13023,6 +13026,7 @@ var BN254_SCALAR_FIELD = BigInt(
13023
13026
  );
13024
13027
  var FINGERPRINT_BITS = 256;
13025
13028
  var DEFAULT_THRESHOLD = 30;
13029
+ var DEFAULT_MIN_DISTANCE = 3;
13026
13030
  var PROOF_A_SIZE = 64;
13027
13031
  var PROOF_B_SIZE = 128;
13028
13032
  var PROOF_C_SIZE = 64;
@@ -13043,8 +13047,7 @@ async function captureAudio(options = {}) {
13043
13047
  const {
13044
13048
  signal,
13045
13049
  minDurationMs = MIN_CAPTURE_MS,
13046
- maxDurationMs = MAX_CAPTURE_MS,
13047
- onAudioLevel
13050
+ maxDurationMs = MAX_CAPTURE_MS
13048
13051
  } = options;
13049
13052
  const stream = await navigator.mediaDevices.getUserMedia({
13050
13053
  audio: {
@@ -13064,13 +13067,7 @@ async function captureAudio(options = {}) {
13064
13067
  const bufferSize = 4096;
13065
13068
  const processor = ctx.createScriptProcessor(bufferSize, 1, 1);
13066
13069
  processor.onaudioprocess = (e2) => {
13067
- const data = e2.inputBuffer.getChannelData(0);
13068
- chunks.push(new Float32Array(data));
13069
- if (onAudioLevel) {
13070
- let sum = 0;
13071
- for (let i = 0; i < data.length; i++) sum += data[i] * data[i];
13072
- onAudioLevel(Math.sqrt(sum / data.length));
13073
- }
13070
+ chunks.push(new Float32Array(e2.inputBuffer.getChannelData(0)));
13074
13071
  };
13075
13072
  source.connect(processor);
13076
13073
  processor.connect(ctx.destination);
@@ -13269,6 +13266,37 @@ function condense(values) {
13269
13266
  kurtosis: kurtosis(values)
13270
13267
  };
13271
13268
  }
13269
+ function entropy(values, bins = 16) {
13270
+ if (values.length < 2) return 0;
13271
+ const min = Math.min(...values);
13272
+ const max = Math.max(...values);
13273
+ if (min === max) return 0;
13274
+ const counts = new Array(bins).fill(0);
13275
+ const range = max - min;
13276
+ for (const v of values) {
13277
+ const idx = Math.min(Math.floor((v - min) / range * bins), bins - 1);
13278
+ counts[idx]++;
13279
+ }
13280
+ let h = 0;
13281
+ for (const c of counts) {
13282
+ if (c > 0) {
13283
+ const p = c / values.length;
13284
+ h -= p * Math.log2(p);
13285
+ }
13286
+ }
13287
+ return h;
13288
+ }
13289
+ function autocorrelation(values, lag = 1) {
13290
+ if (values.length <= lag) return 0;
13291
+ const m = mean(values);
13292
+ const v = variance(values, m);
13293
+ if (v === 0) return 0;
13294
+ let sum = 0;
13295
+ for (let i = 0; i < values.length - lag; i++) {
13296
+ sum += (values[i] - m) * (values[i + lag] - m);
13297
+ }
13298
+ return sum / ((values.length - lag) * v);
13299
+ }
13272
13300
  function fuseFeatures(audio, motion, touch) {
13273
13301
  return [...audio, ...motion, ...touch];
13274
13302
  }
@@ -13283,10 +13311,10 @@ function extractMFCC(audio) {
13283
13311
  try {
13284
13312
  Meyda = require("meyda");
13285
13313
  } catch {
13286
- return new Array(NUM_MFCC * 3 * 4).fill(0);
13314
+ return new Array(NUM_MFCC * 3 * 4 + NUM_MFCC).fill(0);
13287
13315
  }
13288
13316
  const numFrames = Math.floor((samples.length - FRAME_SIZE) / HOP_SIZE) + 1;
13289
- if (numFrames < 3) return new Array(NUM_MFCC * 3 * 4).fill(0);
13317
+ if (numFrames < 3) return new Array(NUM_MFCC * 3 * 4 + NUM_MFCC).fill(0);
13290
13318
  const mfccFrames = [];
13291
13319
  for (let i = 0; i < numFrames; i++) {
13292
13320
  const start = i * HOP_SIZE;
@@ -13321,6 +13349,10 @@ function extractMFCC(audio) {
13321
13349
  const stats = condense(dd);
13322
13350
  features.push(stats.mean, stats.variance, stats.skewness, stats.kurtosis);
13323
13351
  }
13352
+ for (let c = 0; c < NUM_MFCC; c++) {
13353
+ const raw = mfccFrames.map((f) => f[c] ?? 0);
13354
+ features.push(entropy(raw));
13355
+ }
13324
13356
  return features;
13325
13357
  }
13326
13358
  function computeDeltas(frames) {
@@ -13335,7 +13367,7 @@ function computeDeltas(frames) {
13335
13367
 
13336
13368
  // src/extraction/kinematic.ts
13337
13369
  function extractMotionFeatures(samples) {
13338
- if (samples.length < 5) return new Array(48).fill(0);
13370
+ if (samples.length < 5) return new Array(54).fill(0);
13339
13371
  const axes = {
13340
13372
  ax: samples.map((s) => s.ax),
13341
13373
  ay: samples.map((s) => s.ay),
@@ -13361,10 +13393,19 @@ function extractMotionFeatures(samples) {
13361
13393
  jounceStats.kurtosis
13362
13394
  );
13363
13395
  }
13396
+ for (const values of Object.values(axes)) {
13397
+ const jerk = derivative(values);
13398
+ const windowSize = Math.max(5, Math.floor(jerk.length / 4));
13399
+ const windowVariances = [];
13400
+ for (let i = 0; i <= jerk.length - windowSize; i += windowSize) {
13401
+ windowVariances.push(variance(jerk.slice(i, i + windowSize)));
13402
+ }
13403
+ features.push(windowVariances.length >= 2 ? variance(windowVariances) : 0);
13404
+ }
13364
13405
  return features;
13365
13406
  }
13366
13407
  function extractTouchFeatures(samples) {
13367
- if (samples.length < 5) return new Array(32).fill(0);
13408
+ if (samples.length < 5) return new Array(36).fill(0);
13368
13409
  const x = samples.map((s) => s.x);
13369
13410
  const y = samples.map((s) => s.y);
13370
13411
  const pressure = samples.map((s) => s.pressure);
@@ -13384,6 +13425,14 @@ function extractTouchFeatures(samples) {
13384
13425
  const jerkY = derivative(accY);
13385
13426
  features.push(...Object.values(condense(jerkX)));
13386
13427
  features.push(...Object.values(condense(jerkY)));
13428
+ for (const values of [vx, vy, pressure, area]) {
13429
+ const windowSize = Math.max(5, Math.floor(values.length / 4));
13430
+ const windowVariances = [];
13431
+ for (let i = 0; i <= values.length - windowSize; i += windowSize) {
13432
+ windowVariances.push(variance(values.slice(i, i + windowSize)));
13433
+ }
13434
+ features.push(windowVariances.length >= 2 ? variance(windowVariances) : 0);
13435
+ }
13387
13436
  return features;
13388
13437
  }
13389
13438
  function derivative(values) {
@@ -13565,7 +13614,7 @@ async function getSnarkjs() {
13565
13614
  }
13566
13615
  return snarkjsModule;
13567
13616
  }
13568
- function prepareCircuitInput(current, previous, threshold = DEFAULT_THRESHOLD) {
13617
+ function prepareCircuitInput(current, previous, threshold = DEFAULT_THRESHOLD, minDistance = DEFAULT_MIN_DISTANCE) {
13569
13618
  return {
13570
13619
  ft_new: current.fingerprint,
13571
13620
  ft_prev: previous.fingerprint,
@@ -13573,7 +13622,8 @@ function prepareCircuitInput(current, previous, threshold = DEFAULT_THRESHOLD) {
13573
13622
  salt_prev: previous.salt.toString(),
13574
13623
  commitment_new: current.commitment.toString(),
13575
13624
  commitment_prev: previous.commitment.toString(),
13576
- threshold: threshold.toString()
13625
+ threshold: threshold.toString(),
13626
+ min_distance: minDistance.toString()
13577
13627
  };
13578
13628
  }
13579
13629
  async function generateProof(input, wasmPath, zkeyPath) {
@@ -13791,7 +13841,7 @@ var inMemoryStore = null;
13791
13841
 
13792
13842
  // src/pulse.ts
13793
13843
  function extractFeatures(data) {
13794
- const audioFeatures = data.audio ? extractMFCC(data.audio) : new Array(156).fill(0);
13844
+ const audioFeatures = data.audio ? extractMFCC(data.audio) : new Array(169).fill(0);
13795
13845
  const motionFeatures = extractMotionFeatures(data.motion);
13796
13846
  const touchFeatures = extractTouchFeatures(data.touch);
13797
13847
  return fuseFeatures(audioFeatures, motionFeatures, touchFeatures);
@@ -13887,14 +13937,13 @@ var PulseSession = class {
13887
13937
  this.touchElement = touchElement;
13888
13938
  }
13889
13939
  // --- Audio ---
13890
- async startAudio(onAudioLevel) {
13940
+ async startAudio() {
13891
13941
  if (this.audioStageState !== "idle")
13892
13942
  throw new Error("Audio capture already started");
13893
13943
  this.audioStageState = "capturing";
13894
13944
  this.audioController = new AbortController();
13895
13945
  this.audioPromise = captureAudio({
13896
- signal: this.audioController.signal,
13897
- onAudioLevel
13946
+ signal: this.audioController.signal
13898
13947
  }).catch(() => null);
13899
13948
  }
13900
13949
  async stopAudio() {
@@ -14175,6 +14224,7 @@ function generateLissajousPoints(params) {
14175
14224
  // Annotate the CommonJS export names for ESM import in node:
14176
14225
  0 && (module.exports = {
14177
14226
  DEFAULT_CAPTURE_MS,
14227
+ DEFAULT_MIN_DISTANCE,
14178
14228
  DEFAULT_THRESHOLD,
14179
14229
  FINGERPRINT_BITS,
14180
14230
  MAX_CAPTURE_MS,
@@ -14182,9 +14232,11 @@ function generateLissajousPoints(params) {
14182
14232
  PROGRAM_IDS,
14183
14233
  PulseSDK,
14184
14234
  PulseSession,
14235
+ autocorrelation,
14185
14236
  bigintToBytes32,
14186
14237
  computeCommitment,
14187
14238
  condense,
14239
+ entropy,
14188
14240
  fetchIdentityState,
14189
14241
  fuseFeatures,
14190
14242
  generateLissajousPoints,