@entros/pulse-sdk 1.0.0 → 1.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/LICENSE +1 -1
- package/README.md +2 -2
- package/dist/index.d.mts +25 -12
- package/dist/index.d.ts +25 -12
- package/dist/index.js +118 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +115 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -3
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -38,8 +38,8 @@ import { PulseSDK } from '@entros/pulse-sdk';
|
|
|
38
38
|
const pulse = new PulseSDK({
|
|
39
39
|
cluster: 'devnet',
|
|
40
40
|
relayerUrl: 'https://api.entros.io/relay',
|
|
41
|
-
wasmUrl: '/circuits/
|
|
42
|
-
zkeyUrl: '/circuits/
|
|
41
|
+
wasmUrl: '/circuits/entros_hamming.wasm',
|
|
42
|
+
zkeyUrl: '/circuits/entros_hamming_final.zkey',
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
const result = await pulse.verify(touchElement);
|
package/dist/index.d.mts
CHANGED
|
@@ -95,6 +95,13 @@ interface VerificationResult {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
type ResolvedConfig = Required<Pick<PulseConfig, "cluster" | "threshold">> & PulseConfig;
|
|
98
|
+
/**
|
|
99
|
+
* Shared pipeline: features → simhash → TBH → proof → submit.
|
|
100
|
+
* Used by both PulseSDK.verify() and PulseSession.complete().
|
|
101
|
+
*/
|
|
102
|
+
declare const MIN_AUDIO_SAMPLES = 16000;
|
|
103
|
+
declare const MIN_MOTION_SAMPLES = 10;
|
|
104
|
+
declare const MIN_TOUCH_SAMPLES = 10;
|
|
98
105
|
/**
|
|
99
106
|
* PulseSession — event-driven staged capture session.
|
|
100
107
|
*
|
|
@@ -402,8 +409,8 @@ declare function prepareCircuitInput(current: TBH, previous: TBH, threshold?: nu
|
|
|
402
409
|
* Generate a Groth16 proof for the Hamming distance circuit.
|
|
403
410
|
*
|
|
404
411
|
* @param input - Circuit input (fingerprints, salts, commitments, threshold)
|
|
405
|
-
* @param wasmPath - Path or URL to
|
|
406
|
-
* @param zkeyPath - Path or URL to
|
|
412
|
+
* @param wasmPath - Path or URL to entros_hamming.wasm
|
|
413
|
+
* @param zkeyPath - Path or URL to entros_hamming_final.zkey
|
|
407
414
|
*/
|
|
408
415
|
declare function generateProof(input: CircuitInput, wasmPath: string, zkeyPath: string): Promise<ProofResult>;
|
|
409
416
|
/**
|
|
@@ -512,7 +519,7 @@ declare function attestAgentOperator(agentAsset: string, options: {
|
|
|
512
519
|
/**
|
|
513
520
|
* Query whether an AI agent has a verified human operator via Entros.
|
|
514
521
|
*
|
|
515
|
-
* Reads the "
|
|
522
|
+
* Reads the "entros:human-operator" metadata from the agent's on-chain record
|
|
516
523
|
* and returns the operator's Entros Anchor details.
|
|
517
524
|
*
|
|
518
525
|
* @param agentAsset - Base58 pubkey of the agent's Metaplex Core NFT
|
|
@@ -562,8 +569,13 @@ declare function storeVerificationData(data: StoredVerificationData): Promise<vo
|
|
|
562
569
|
declare function loadVerificationData(): Promise<StoredVerificationData | null>;
|
|
563
570
|
|
|
564
571
|
/**
|
|
565
|
-
*
|
|
566
|
-
*
|
|
572
|
+
* FALLBACK challenge-phrase generator. Used only when the executor's
|
|
573
|
+
* `/challenge` endpoint is unreachable; the authoritative phrase comes from
|
|
574
|
+
* the server (5 real words drawn from a curated English-word dictionary). On
|
|
575
|
+
* this fallback path, validation skips server-side phrase content binding —
|
|
576
|
+
* Tier 1 acoustic + Tier 2 cross-modal still run.
|
|
577
|
+
*
|
|
578
|
+
* Output is 5-6 syllable pairs, forming nonsensical but speakable words.
|
|
567
579
|
* Uses crypto.getRandomValues for unpredictable challenge generation.
|
|
568
580
|
*/
|
|
569
581
|
declare function generatePhrase(wordCount?: number): string;
|
|
@@ -610,11 +622,12 @@ declare function generateLissajousSequence(count?: number): {
|
|
|
610
622
|
/**
|
|
611
623
|
* Fetch the server-issued challenge from the executor.
|
|
612
624
|
*
|
|
613
|
-
* The executor's `/challenge` endpoint returns a fresh nonce +
|
|
614
|
-
* bound to the wallet for a short TTL (default 60s). The phrase is
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
625
|
+
* The executor's `/challenge` endpoint returns a fresh nonce + 5-word phrase
|
|
626
|
+
* bound to the wallet for a short TTL (default 60s). The phrase is drawn from
|
|
627
|
+
* a curated English-word dictionary (source of truth at
|
|
628
|
+
* `entros-validation/src/word_dict.rs`); shown to the user as the voice challenge
|
|
629
|
+
* and looked up server-side at `/validate-features` to verify the audio
|
|
630
|
+
* matches the issued phrase (master-list #89, phrase content binding).
|
|
618
631
|
*
|
|
619
632
|
* Server-issued phrases are the only safe design for content binding: if the
|
|
620
633
|
* client generated the phrase and sent it to the server alongside the audio,
|
|
@@ -628,7 +641,7 @@ declare function generateLissajousSequence(count?: number): {
|
|
|
628
641
|
interface ChallengeResponse {
|
|
629
642
|
/** 32-byte nonce used for on-chain `create_challenge` and the `/attest` handshake. */
|
|
630
643
|
nonce: Uint8Array;
|
|
631
|
-
/**
|
|
644
|
+
/** Server-issued 5-word challenge phrase (drawn from a curated English-word dictionary) the user must speak aloud. */
|
|
632
645
|
phrase: string;
|
|
633
646
|
/** Nonce TTL in seconds (default 60). */
|
|
634
647
|
expiresIn: number;
|
|
@@ -662,4 +675,4 @@ declare function fetchChallenge(executorUrl: string, walletAddress: string, apiK
|
|
|
662
675
|
*/
|
|
663
676
|
declare function encodeAudioAsBase64(samples: Float32Array): string;
|
|
664
677
|
|
|
665
|
-
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_CAPTURE_MS, 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 };
|
|
678
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -95,6 +95,13 @@ interface VerificationResult {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
type ResolvedConfig = Required<Pick<PulseConfig, "cluster" | "threshold">> & PulseConfig;
|
|
98
|
+
/**
|
|
99
|
+
* Shared pipeline: features → simhash → TBH → proof → submit.
|
|
100
|
+
* Used by both PulseSDK.verify() and PulseSession.complete().
|
|
101
|
+
*/
|
|
102
|
+
declare const MIN_AUDIO_SAMPLES = 16000;
|
|
103
|
+
declare const MIN_MOTION_SAMPLES = 10;
|
|
104
|
+
declare const MIN_TOUCH_SAMPLES = 10;
|
|
98
105
|
/**
|
|
99
106
|
* PulseSession — event-driven staged capture session.
|
|
100
107
|
*
|
|
@@ -402,8 +409,8 @@ declare function prepareCircuitInput(current: TBH, previous: TBH, threshold?: nu
|
|
|
402
409
|
* Generate a Groth16 proof for the Hamming distance circuit.
|
|
403
410
|
*
|
|
404
411
|
* @param input - Circuit input (fingerprints, salts, commitments, threshold)
|
|
405
|
-
* @param wasmPath - Path or URL to
|
|
406
|
-
* @param zkeyPath - Path or URL to
|
|
412
|
+
* @param wasmPath - Path or URL to entros_hamming.wasm
|
|
413
|
+
* @param zkeyPath - Path or URL to entros_hamming_final.zkey
|
|
407
414
|
*/
|
|
408
415
|
declare function generateProof(input: CircuitInput, wasmPath: string, zkeyPath: string): Promise<ProofResult>;
|
|
409
416
|
/**
|
|
@@ -512,7 +519,7 @@ declare function attestAgentOperator(agentAsset: string, options: {
|
|
|
512
519
|
/**
|
|
513
520
|
* Query whether an AI agent has a verified human operator via Entros.
|
|
514
521
|
*
|
|
515
|
-
* Reads the "
|
|
522
|
+
* Reads the "entros:human-operator" metadata from the agent's on-chain record
|
|
516
523
|
* and returns the operator's Entros Anchor details.
|
|
517
524
|
*
|
|
518
525
|
* @param agentAsset - Base58 pubkey of the agent's Metaplex Core NFT
|
|
@@ -562,8 +569,13 @@ declare function storeVerificationData(data: StoredVerificationData): Promise<vo
|
|
|
562
569
|
declare function loadVerificationData(): Promise<StoredVerificationData | null>;
|
|
563
570
|
|
|
564
571
|
/**
|
|
565
|
-
*
|
|
566
|
-
*
|
|
572
|
+
* FALLBACK challenge-phrase generator. Used only when the executor's
|
|
573
|
+
* `/challenge` endpoint is unreachable; the authoritative phrase comes from
|
|
574
|
+
* the server (5 real words drawn from a curated English-word dictionary). On
|
|
575
|
+
* this fallback path, validation skips server-side phrase content binding —
|
|
576
|
+
* Tier 1 acoustic + Tier 2 cross-modal still run.
|
|
577
|
+
*
|
|
578
|
+
* Output is 5-6 syllable pairs, forming nonsensical but speakable words.
|
|
567
579
|
* Uses crypto.getRandomValues for unpredictable challenge generation.
|
|
568
580
|
*/
|
|
569
581
|
declare function generatePhrase(wordCount?: number): string;
|
|
@@ -610,11 +622,12 @@ declare function generateLissajousSequence(count?: number): {
|
|
|
610
622
|
/**
|
|
611
623
|
* Fetch the server-issued challenge from the executor.
|
|
612
624
|
*
|
|
613
|
-
* The executor's `/challenge` endpoint returns a fresh nonce +
|
|
614
|
-
* bound to the wallet for a short TTL (default 60s). The phrase is
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
625
|
+
* The executor's `/challenge` endpoint returns a fresh nonce + 5-word phrase
|
|
626
|
+
* bound to the wallet for a short TTL (default 60s). The phrase is drawn from
|
|
627
|
+
* a curated English-word dictionary (source of truth at
|
|
628
|
+
* `entros-validation/src/word_dict.rs`); shown to the user as the voice challenge
|
|
629
|
+
* and looked up server-side at `/validate-features` to verify the audio
|
|
630
|
+
* matches the issued phrase (master-list #89, phrase content binding).
|
|
618
631
|
*
|
|
619
632
|
* Server-issued phrases are the only safe design for content binding: if the
|
|
620
633
|
* client generated the phrase and sent it to the server alongside the audio,
|
|
@@ -628,7 +641,7 @@ declare function generateLissajousSequence(count?: number): {
|
|
|
628
641
|
interface ChallengeResponse {
|
|
629
642
|
/** 32-byte nonce used for on-chain `create_challenge` and the `/attest` handshake. */
|
|
630
643
|
nonce: Uint8Array;
|
|
631
|
-
/**
|
|
644
|
+
/** Server-issued 5-word challenge phrase (drawn from a curated English-word dictionary) the user must speak aloud. */
|
|
632
645
|
phrase: string;
|
|
633
646
|
/** Nonce TTL in seconds (default 60). */
|
|
634
647
|
expiresIn: number;
|
|
@@ -662,4 +675,4 @@ declare function fetchChallenge(executorUrl: string, walletAddress: string, apiK
|
|
|
662
675
|
*/
|
|
663
676
|
declare function encodeAudioAsBase64(samples: Float32Array): string;
|
|
664
677
|
|
|
665
|
-
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_CAPTURE_MS, 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 };
|
|
678
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,10 @@ __export(index_exports, {
|
|
|
35
35
|
DEFAULT_THRESHOLD: () => DEFAULT_THRESHOLD,
|
|
36
36
|
FINGERPRINT_BITS: () => FINGERPRINT_BITS,
|
|
37
37
|
MAX_CAPTURE_MS: () => MAX_CAPTURE_MS,
|
|
38
|
+
MIN_AUDIO_SAMPLES: () => MIN_AUDIO_SAMPLES,
|
|
38
39
|
MIN_CAPTURE_MS: () => MIN_CAPTURE_MS,
|
|
40
|
+
MIN_MOTION_SAMPLES: () => MIN_MOTION_SAMPLES,
|
|
41
|
+
MIN_TOUCH_SAMPLES: () => MIN_TOUCH_SAMPLES,
|
|
39
42
|
PROGRAM_IDS: () => PROGRAM_IDS,
|
|
40
43
|
PulseSDK: () => PulseSDK,
|
|
41
44
|
PulseSession: () => PulseSession,
|
|
@@ -113,7 +116,7 @@ var PROGRAM_IDS = {
|
|
|
113
116
|
var AGENT_REGISTRY_CONFIG = {
|
|
114
117
|
programIdDevnet: "8oo4J9tBB3Hna1jRQ3rWvJjojqM5DYTDJo5cejUuJy3C",
|
|
115
118
|
programIdMainnet: "8oo4dC4JvBLwy5tGgiH3WwK4B9PWxL9Z4XjA2jzkQMbQ",
|
|
116
|
-
metadataKey: "
|
|
119
|
+
metadataKey: "entros:human-operator"
|
|
117
120
|
};
|
|
118
121
|
var SAS_CONFIG = {
|
|
119
122
|
programId: "22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG",
|
|
@@ -2115,7 +2118,7 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
|
|
|
2115
2118
|
success: false,
|
|
2116
2119
|
commitment: tbh.commitmentBytes,
|
|
2117
2120
|
isFirstVerification: false,
|
|
2118
|
-
error: "Re-verification requires wasmUrl and zkeyUrl in PulseConfig. Host the
|
|
2121
|
+
error: "Re-verification requires wasmUrl and zkeyUrl in PulseConfig. Host the entros_hamming.wasm and entros_hamming_final.zkey circuit artifacts at public URLs."
|
|
2119
2122
|
};
|
|
2120
2123
|
}
|
|
2121
2124
|
try {
|
|
@@ -2403,6 +2406,116 @@ var PulseSession = class {
|
|
|
2403
2406
|
);
|
|
2404
2407
|
this.touchStageState = "skipped";
|
|
2405
2408
|
}
|
|
2409
|
+
// --- Test hooks (internal builds only) ---
|
|
2410
|
+
/**
|
|
2411
|
+
* @internal Test-only. Primes the session with pre-captured sensor data,
|
|
2412
|
+
* bypassing browser capture APIs. Throws unless built with IAM_INTERNAL_TEST=1.
|
|
2413
|
+
* Stripped from the published .d.ts so npm consumers never see it. Used by the
|
|
2414
|
+
* red team harness to drive the real verification pipeline (extraction →
|
|
2415
|
+
* SimHash → TBH → proof → submit) against synthetic sensor data — never
|
|
2416
|
+
* available to npm consumers.
|
|
2417
|
+
*/
|
|
2418
|
+
__injectSensorData(data) {
|
|
2419
|
+
if (true) {
|
|
2420
|
+
throw new Error(
|
|
2421
|
+
"PulseSession.__injectSensorData is only available in internal test builds. Set IAM_INTERNAL_TEST=1 when building pulse-sdk from source."
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
const conflicts = [];
|
|
2425
|
+
if (this.audioStageState === "capturing") conflicts.push("audio");
|
|
2426
|
+
if (this.motionStageState === "capturing") conflicts.push("motion");
|
|
2427
|
+
if (this.touchStageState === "capturing") conflicts.push("touch");
|
|
2428
|
+
if (conflicts.length > 0) {
|
|
2429
|
+
throw new Error(
|
|
2430
|
+
`__injectSensorData: cannot inject while stages are capturing: ${conflicts.join(", ")}. Create a fresh session via sdk.createSession() and inject before any startAudio/startMotion/startTouch call.`
|
|
2431
|
+
);
|
|
2432
|
+
}
|
|
2433
|
+
if (!data.audio || data.audio.samples.length < MIN_AUDIO_SAMPLES) {
|
|
2434
|
+
throw new Error(
|
|
2435
|
+
`__injectSensorData: audio required, minimum ${MIN_AUDIO_SAMPLES} samples (got ${data.audio?.samples.length ?? 0}).`
|
|
2436
|
+
);
|
|
2437
|
+
}
|
|
2438
|
+
if (data.motion.length < MIN_MOTION_SAMPLES) {
|
|
2439
|
+
throw new Error(
|
|
2440
|
+
`__injectSensorData: motion required, minimum ${MIN_MOTION_SAMPLES} samples (got ${data.motion.length}).`
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
if (data.touch.length < MIN_TOUCH_SAMPLES) {
|
|
2444
|
+
throw new Error(
|
|
2445
|
+
`__injectSensorData: touch required, minimum ${MIN_TOUCH_SAMPLES} samples (got ${data.touch.length}).`
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
this.audioData = data.audio;
|
|
2449
|
+
this.motionData = data.motion;
|
|
2450
|
+
this.touchData = data.touch;
|
|
2451
|
+
this.audioStageState = "captured";
|
|
2452
|
+
this.motionStageState = "captured";
|
|
2453
|
+
this.touchStageState = "captured";
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* @internal
|
|
2457
|
+
*
|
|
2458
|
+
* Run the validation step of the verify pipeline only: feature extraction
|
|
2459
|
+
* + `/validate-features` POST. Returns the validation outcome without ever
|
|
2460
|
+
* touching the on-chain submission path. Mirrors the production user
|
|
2461
|
+
* flow's pre-payment gate — the validation server runs without requiring
|
|
2462
|
+
* the wallet to have SOL, just like a real user gets a validation result
|
|
2463
|
+
* before being prompted to sign the on-chain mint.
|
|
2464
|
+
*
|
|
2465
|
+
* Note: this is a strict subset of `complete()`. It skips the data-quality
|
|
2466
|
+
* gates and re-verification check that `processSensorData` performs. The
|
|
2467
|
+
* validation server still runs its full pipeline (Tier 1 + Tier 2 +
|
|
2468
|
+
* phrase binding); only the client-side pre-flight checks differ.
|
|
2469
|
+
*
|
|
2470
|
+
* Use case: red team campaigns measuring server-side validation at scale
|
|
2471
|
+
* without per-attempt SOL funding. Build-time gated identically to
|
|
2472
|
+
* `__injectSensorData`; throws in production builds.
|
|
2473
|
+
*/
|
|
2474
|
+
async __validateOnly(walletAddress) {
|
|
2475
|
+
if (true) {
|
|
2476
|
+
throw new Error(
|
|
2477
|
+
"PulseSession.__validateOnly is only available in internal test builds. Set IAM_INTERNAL_TEST=1 when building pulse-sdk from source."
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
if (typeof walletAddress !== "string" || walletAddress.length === 0) {
|
|
2481
|
+
throw new Error(
|
|
2482
|
+
"__validateOnly requires a non-empty walletAddress string (used as wallet_id in the /validate-features payload)."
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
const active = [];
|
|
2486
|
+
if (this.audioStageState === "capturing") active.push("audio");
|
|
2487
|
+
if (this.motionStageState === "capturing") active.push("motion");
|
|
2488
|
+
if (this.touchStageState === "capturing") active.push("touch");
|
|
2489
|
+
if (active.length > 0) {
|
|
2490
|
+
throw new Error(
|
|
2491
|
+
`Cannot validate: stages still capturing: ${active.join(", ")}`
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
if (!this.audioData || this.motionData.length === 0 || this.touchData.length === 0) {
|
|
2495
|
+
throw new Error(
|
|
2496
|
+
"__validateOnly requires sensor data first \u2014 call __injectSensorData() before this."
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
const sensorData = {
|
|
2500
|
+
audio: this.audioData,
|
|
2501
|
+
motion: this.motionData,
|
|
2502
|
+
touch: this.touchData,
|
|
2503
|
+
modalities: {
|
|
2504
|
+
audio: true,
|
|
2505
|
+
motion: true,
|
|
2506
|
+
touch: true
|
|
2507
|
+
}
|
|
2508
|
+
};
|
|
2509
|
+
const extraction = await extractFingerprintAndValidate(
|
|
2510
|
+
sensorData,
|
|
2511
|
+
this.config,
|
|
2512
|
+
walletAddress
|
|
2513
|
+
);
|
|
2514
|
+
if (!extraction.ok) {
|
|
2515
|
+
return { validated: false, error: extraction.error };
|
|
2516
|
+
}
|
|
2517
|
+
return { validated: true };
|
|
2518
|
+
}
|
|
2406
2519
|
// --- Complete ---
|
|
2407
2520
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Solana types are optional peer deps
|
|
2408
2521
|
async complete(wallet, connection, onProgress) {
|
|
@@ -3078,7 +3191,10 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
|
|
|
3078
3191
|
DEFAULT_THRESHOLD,
|
|
3079
3192
|
FINGERPRINT_BITS,
|
|
3080
3193
|
MAX_CAPTURE_MS,
|
|
3194
|
+
MIN_AUDIO_SAMPLES,
|
|
3081
3195
|
MIN_CAPTURE_MS,
|
|
3196
|
+
MIN_MOTION_SAMPLES,
|
|
3197
|
+
MIN_TOUCH_SAMPLES,
|
|
3082
3198
|
PROGRAM_IDS,
|
|
3083
3199
|
PulseSDK,
|
|
3084
3200
|
PulseSession,
|