@iam-protocol/pulse-sdk 0.1.1 → 0.2.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/dist/index.d.mts +30 -5
- package/dist/index.d.ts +30 -5
- package/dist/index.js +124 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +119 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/challenge/lissajous.ts +29 -0
- package/src/challenge/phrase.ts +34 -1
- package/src/config.ts +2 -1
- package/src/extraction/kinematic.ts +28 -5
- package/src/extraction/mfcc.ts +11 -4
- package/src/extraction/statistics.ts +46 -0
- package/src/index.ts +4 -4
- package/src/proof/prover.ts +4 -2
- package/src/proof/types.ts +1 -0
- package/src/pulse.ts +2 -3
- package/src/sensor/audio.ts +1 -9
- package/src/sensor/types.ts +0 -2
- package/test/integration.test.ts +3 -2
- package/test/serializer.test.ts +1 -0
package/dist/index.mjs
CHANGED
|
@@ -12946,6 +12946,7 @@ var BN254_SCALAR_FIELD = BigInt(
|
|
|
12946
12946
|
);
|
|
12947
12947
|
var FINGERPRINT_BITS = 256;
|
|
12948
12948
|
var DEFAULT_THRESHOLD = 30;
|
|
12949
|
+
var DEFAULT_MIN_DISTANCE = 3;
|
|
12949
12950
|
var PROOF_A_SIZE = 64;
|
|
12950
12951
|
var PROOF_B_SIZE = 128;
|
|
12951
12952
|
var PROOF_C_SIZE = 64;
|
|
@@ -12966,8 +12967,7 @@ async function captureAudio(options = {}) {
|
|
|
12966
12967
|
const {
|
|
12967
12968
|
signal,
|
|
12968
12969
|
minDurationMs = MIN_CAPTURE_MS,
|
|
12969
|
-
maxDurationMs = MAX_CAPTURE_MS
|
|
12970
|
-
onAudioLevel
|
|
12970
|
+
maxDurationMs = MAX_CAPTURE_MS
|
|
12971
12971
|
} = options;
|
|
12972
12972
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
12973
12973
|
audio: {
|
|
@@ -12987,13 +12987,7 @@ async function captureAudio(options = {}) {
|
|
|
12987
12987
|
const bufferSize = 4096;
|
|
12988
12988
|
const processor = ctx.createScriptProcessor(bufferSize, 1, 1);
|
|
12989
12989
|
processor.onaudioprocess = (e2) => {
|
|
12990
|
-
|
|
12991
|
-
chunks.push(new Float32Array(data));
|
|
12992
|
-
if (onAudioLevel) {
|
|
12993
|
-
let sum = 0;
|
|
12994
|
-
for (let i = 0; i < data.length; i++) sum += data[i] * data[i];
|
|
12995
|
-
onAudioLevel(Math.sqrt(sum / data.length));
|
|
12996
|
-
}
|
|
12990
|
+
chunks.push(new Float32Array(e2.inputBuffer.getChannelData(0)));
|
|
12997
12991
|
};
|
|
12998
12992
|
source.connect(processor);
|
|
12999
12993
|
processor.connect(ctx.destination);
|
|
@@ -13192,6 +13186,37 @@ function condense(values) {
|
|
|
13192
13186
|
kurtosis: kurtosis(values)
|
|
13193
13187
|
};
|
|
13194
13188
|
}
|
|
13189
|
+
function entropy(values, bins = 16) {
|
|
13190
|
+
if (values.length < 2) return 0;
|
|
13191
|
+
const min = Math.min(...values);
|
|
13192
|
+
const max = Math.max(...values);
|
|
13193
|
+
if (min === max) return 0;
|
|
13194
|
+
const counts = new Array(bins).fill(0);
|
|
13195
|
+
const range = max - min;
|
|
13196
|
+
for (const v of values) {
|
|
13197
|
+
const idx = Math.min(Math.floor((v - min) / range * bins), bins - 1);
|
|
13198
|
+
counts[idx]++;
|
|
13199
|
+
}
|
|
13200
|
+
let h = 0;
|
|
13201
|
+
for (const c of counts) {
|
|
13202
|
+
if (c > 0) {
|
|
13203
|
+
const p = c / values.length;
|
|
13204
|
+
h -= p * Math.log2(p);
|
|
13205
|
+
}
|
|
13206
|
+
}
|
|
13207
|
+
return h;
|
|
13208
|
+
}
|
|
13209
|
+
function autocorrelation(values, lag = 1) {
|
|
13210
|
+
if (values.length <= lag) return 0;
|
|
13211
|
+
const m = mean(values);
|
|
13212
|
+
const v = variance(values, m);
|
|
13213
|
+
if (v === 0) return 0;
|
|
13214
|
+
let sum = 0;
|
|
13215
|
+
for (let i = 0; i < values.length - lag; i++) {
|
|
13216
|
+
sum += (values[i] - m) * (values[i + lag] - m);
|
|
13217
|
+
}
|
|
13218
|
+
return sum / ((values.length - lag) * v);
|
|
13219
|
+
}
|
|
13195
13220
|
function fuseFeatures(audio, motion, touch) {
|
|
13196
13221
|
return [...audio, ...motion, ...touch];
|
|
13197
13222
|
}
|
|
@@ -13206,10 +13231,10 @@ function extractMFCC(audio) {
|
|
|
13206
13231
|
try {
|
|
13207
13232
|
Meyda = __require("meyda");
|
|
13208
13233
|
} catch {
|
|
13209
|
-
return new Array(NUM_MFCC * 3 * 4).fill(0);
|
|
13234
|
+
return new Array(NUM_MFCC * 3 * 4 + NUM_MFCC).fill(0);
|
|
13210
13235
|
}
|
|
13211
13236
|
const numFrames = Math.floor((samples.length - FRAME_SIZE) / HOP_SIZE) + 1;
|
|
13212
|
-
if (numFrames < 3) return new Array(NUM_MFCC * 3 * 4).fill(0);
|
|
13237
|
+
if (numFrames < 3) return new Array(NUM_MFCC * 3 * 4 + NUM_MFCC).fill(0);
|
|
13213
13238
|
const mfccFrames = [];
|
|
13214
13239
|
for (let i = 0; i < numFrames; i++) {
|
|
13215
13240
|
const start = i * HOP_SIZE;
|
|
@@ -13244,6 +13269,10 @@ function extractMFCC(audio) {
|
|
|
13244
13269
|
const stats = condense(dd);
|
|
13245
13270
|
features.push(stats.mean, stats.variance, stats.skewness, stats.kurtosis);
|
|
13246
13271
|
}
|
|
13272
|
+
for (let c = 0; c < NUM_MFCC; c++) {
|
|
13273
|
+
const raw = mfccFrames.map((f) => f[c] ?? 0);
|
|
13274
|
+
features.push(entropy(raw));
|
|
13275
|
+
}
|
|
13247
13276
|
return features;
|
|
13248
13277
|
}
|
|
13249
13278
|
function computeDeltas(frames) {
|
|
@@ -13258,7 +13287,7 @@ function computeDeltas(frames) {
|
|
|
13258
13287
|
|
|
13259
13288
|
// src/extraction/kinematic.ts
|
|
13260
13289
|
function extractMotionFeatures(samples) {
|
|
13261
|
-
if (samples.length < 5) return new Array(
|
|
13290
|
+
if (samples.length < 5) return new Array(54).fill(0);
|
|
13262
13291
|
const axes = {
|
|
13263
13292
|
ax: samples.map((s) => s.ax),
|
|
13264
13293
|
ay: samples.map((s) => s.ay),
|
|
@@ -13284,10 +13313,19 @@ function extractMotionFeatures(samples) {
|
|
|
13284
13313
|
jounceStats.kurtosis
|
|
13285
13314
|
);
|
|
13286
13315
|
}
|
|
13316
|
+
for (const values of Object.values(axes)) {
|
|
13317
|
+
const jerk = derivative(values);
|
|
13318
|
+
const windowSize = Math.max(5, Math.floor(jerk.length / 4));
|
|
13319
|
+
const windowVariances = [];
|
|
13320
|
+
for (let i = 0; i <= jerk.length - windowSize; i += windowSize) {
|
|
13321
|
+
windowVariances.push(variance(jerk.slice(i, i + windowSize)));
|
|
13322
|
+
}
|
|
13323
|
+
features.push(windowVariances.length >= 2 ? variance(windowVariances) : 0);
|
|
13324
|
+
}
|
|
13287
13325
|
return features;
|
|
13288
13326
|
}
|
|
13289
13327
|
function extractTouchFeatures(samples) {
|
|
13290
|
-
if (samples.length < 5) return new Array(
|
|
13328
|
+
if (samples.length < 5) return new Array(36).fill(0);
|
|
13291
13329
|
const x = samples.map((s) => s.x);
|
|
13292
13330
|
const y = samples.map((s) => s.y);
|
|
13293
13331
|
const pressure = samples.map((s) => s.pressure);
|
|
@@ -13307,6 +13345,14 @@ function extractTouchFeatures(samples) {
|
|
|
13307
13345
|
const jerkY = derivative(accY);
|
|
13308
13346
|
features.push(...Object.values(condense(jerkX)));
|
|
13309
13347
|
features.push(...Object.values(condense(jerkY)));
|
|
13348
|
+
for (const values of [vx, vy, pressure, area]) {
|
|
13349
|
+
const windowSize = Math.max(5, Math.floor(values.length / 4));
|
|
13350
|
+
const windowVariances = [];
|
|
13351
|
+
for (let i = 0; i <= values.length - windowSize; i += windowSize) {
|
|
13352
|
+
windowVariances.push(variance(values.slice(i, i + windowSize)));
|
|
13353
|
+
}
|
|
13354
|
+
features.push(windowVariances.length >= 2 ? variance(windowVariances) : 0);
|
|
13355
|
+
}
|
|
13310
13356
|
return features;
|
|
13311
13357
|
}
|
|
13312
13358
|
function derivative(values) {
|
|
@@ -13488,7 +13534,7 @@ async function getSnarkjs() {
|
|
|
13488
13534
|
}
|
|
13489
13535
|
return snarkjsModule;
|
|
13490
13536
|
}
|
|
13491
|
-
function prepareCircuitInput(current, previous, threshold = DEFAULT_THRESHOLD) {
|
|
13537
|
+
function prepareCircuitInput(current, previous, threshold = DEFAULT_THRESHOLD, minDistance = DEFAULT_MIN_DISTANCE) {
|
|
13492
13538
|
return {
|
|
13493
13539
|
ft_new: current.fingerprint,
|
|
13494
13540
|
ft_prev: previous.fingerprint,
|
|
@@ -13496,7 +13542,8 @@ function prepareCircuitInput(current, previous, threshold = DEFAULT_THRESHOLD) {
|
|
|
13496
13542
|
salt_prev: previous.salt.toString(),
|
|
13497
13543
|
commitment_new: current.commitment.toString(),
|
|
13498
13544
|
commitment_prev: previous.commitment.toString(),
|
|
13499
|
-
threshold: threshold.toString()
|
|
13545
|
+
threshold: threshold.toString(),
|
|
13546
|
+
min_distance: minDistance.toString()
|
|
13500
13547
|
};
|
|
13501
13548
|
}
|
|
13502
13549
|
async function generateProof(input, wasmPath, zkeyPath) {
|
|
@@ -13714,7 +13761,7 @@ var inMemoryStore = null;
|
|
|
13714
13761
|
|
|
13715
13762
|
// src/pulse.ts
|
|
13716
13763
|
function extractFeatures(data) {
|
|
13717
|
-
const audioFeatures = data.audio ? extractMFCC(data.audio) : new Array(
|
|
13764
|
+
const audioFeatures = data.audio ? extractMFCC(data.audio) : new Array(169).fill(0);
|
|
13718
13765
|
const motionFeatures = extractMotionFeatures(data.motion);
|
|
13719
13766
|
const touchFeatures = extractTouchFeatures(data.touch);
|
|
13720
13767
|
return fuseFeatures(audioFeatures, motionFeatures, touchFeatures);
|
|
@@ -13810,14 +13857,13 @@ var PulseSession = class {
|
|
|
13810
13857
|
this.touchElement = touchElement;
|
|
13811
13858
|
}
|
|
13812
13859
|
// --- Audio ---
|
|
13813
|
-
async startAudio(
|
|
13860
|
+
async startAudio() {
|
|
13814
13861
|
if (this.audioStageState !== "idle")
|
|
13815
13862
|
throw new Error("Audio capture already started");
|
|
13816
13863
|
this.audioStageState = "capturing";
|
|
13817
13864
|
this.audioController = new AbortController();
|
|
13818
13865
|
this.audioPromise = captureAudio({
|
|
13819
|
-
signal: this.audioController.signal
|
|
13820
|
-
onAudioLevel
|
|
13866
|
+
signal: this.audioController.signal
|
|
13821
13867
|
}).catch(() => null);
|
|
13822
13868
|
}
|
|
13823
13869
|
async stopAudio() {
|
|
@@ -14065,6 +14111,28 @@ function generatePhrase(wordCount = 5) {
|
|
|
14065
14111
|
}
|
|
14066
14112
|
return words.join(" ");
|
|
14067
14113
|
}
|
|
14114
|
+
function generatePhraseSequence(count = 3, wordCount = 4) {
|
|
14115
|
+
const subsetSize = Math.floor(SYLLABLES.length / count);
|
|
14116
|
+
const phrases = [];
|
|
14117
|
+
for (let p = 0; p < count; p++) {
|
|
14118
|
+
const start = p * subsetSize % SYLLABLES.length;
|
|
14119
|
+
const subset = [
|
|
14120
|
+
...SYLLABLES.slice(start, start + subsetSize),
|
|
14121
|
+
...SYLLABLES.slice(0, Math.max(0, start + subsetSize - SYLLABLES.length))
|
|
14122
|
+
];
|
|
14123
|
+
const words = [];
|
|
14124
|
+
for (let w = 0; w < wordCount; w++) {
|
|
14125
|
+
const syllableCount = 2 + Math.floor(Math.random() * 2);
|
|
14126
|
+
let word = "";
|
|
14127
|
+
for (let s = 0; s < syllableCount; s++) {
|
|
14128
|
+
word += subset[Math.floor(Math.random() * subset.length)];
|
|
14129
|
+
}
|
|
14130
|
+
words.push(word);
|
|
14131
|
+
}
|
|
14132
|
+
phrases.push(words.join(" "));
|
|
14133
|
+
}
|
|
14134
|
+
return phrases;
|
|
14135
|
+
}
|
|
14068
14136
|
|
|
14069
14137
|
// src/challenge/lissajous.ts
|
|
14070
14138
|
function randomLissajousParams() {
|
|
@@ -14095,8 +14163,36 @@ function generateLissajousPoints(params) {
|
|
|
14095
14163
|
}
|
|
14096
14164
|
return result;
|
|
14097
14165
|
}
|
|
14166
|
+
function generateLissajousSequence(count = 2) {
|
|
14167
|
+
const allRatios = [
|
|
14168
|
+
[1, 2],
|
|
14169
|
+
[2, 3],
|
|
14170
|
+
[3, 4],
|
|
14171
|
+
[3, 5],
|
|
14172
|
+
[4, 5],
|
|
14173
|
+
[1, 3],
|
|
14174
|
+
[2, 5],
|
|
14175
|
+
[5, 6],
|
|
14176
|
+
[3, 7],
|
|
14177
|
+
[4, 7]
|
|
14178
|
+
];
|
|
14179
|
+
const shuffled = [...allRatios].sort(() => Math.random() - 0.5);
|
|
14180
|
+
const sequence = [];
|
|
14181
|
+
for (let i = 0; i < count; i++) {
|
|
14182
|
+
const pair = shuffled[i % shuffled.length];
|
|
14183
|
+
const params = {
|
|
14184
|
+
a: pair[0],
|
|
14185
|
+
b: pair[1],
|
|
14186
|
+
delta: Math.PI * (0.1 + Math.random() * 0.8),
|
|
14187
|
+
points: 200
|
|
14188
|
+
};
|
|
14189
|
+
sequence.push({ params, points: generateLissajousPoints(params) });
|
|
14190
|
+
}
|
|
14191
|
+
return sequence;
|
|
14192
|
+
}
|
|
14098
14193
|
export {
|
|
14099
14194
|
DEFAULT_CAPTURE_MS,
|
|
14195
|
+
DEFAULT_MIN_DISTANCE,
|
|
14100
14196
|
DEFAULT_THRESHOLD,
|
|
14101
14197
|
FINGERPRINT_BITS,
|
|
14102
14198
|
MAX_CAPTURE_MS,
|
|
@@ -14104,13 +14200,17 @@ export {
|
|
|
14104
14200
|
PROGRAM_IDS,
|
|
14105
14201
|
PulseSDK,
|
|
14106
14202
|
PulseSession,
|
|
14203
|
+
autocorrelation,
|
|
14107
14204
|
bigintToBytes32,
|
|
14108
14205
|
computeCommitment,
|
|
14109
14206
|
condense,
|
|
14207
|
+
entropy,
|
|
14110
14208
|
fetchIdentityState,
|
|
14111
14209
|
fuseFeatures,
|
|
14112
14210
|
generateLissajousPoints,
|
|
14211
|
+
generateLissajousSequence,
|
|
14113
14212
|
generatePhrase,
|
|
14213
|
+
generatePhraseSequence,
|
|
14114
14214
|
generateProof,
|
|
14115
14215
|
generateSalt,
|
|
14116
14216
|
generateSolanaProof,
|