@entros/pulse-sdk 3.3.0 → 3.4.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
@@ -695,6 +695,20 @@ interface BaselineWallet {
695
695
  publicKey: PublicKey;
696
696
  signMessage: (msg: Uint8Array) => Promise<Uint8Array>;
697
697
  }
698
+ /**
699
+ * Thrown when a `signMessage` result fails to verify against the wallet's own
700
+ * public key — i.e. a different wallet signed than the one connected. The most
701
+ * common cause is a second wallet extension (e.g. Backpack) set as the browser
702
+ * default and intercepting the prompt while a different wallet (e.g. Phantom)
703
+ * is the one actually connected. Surfaced so callers can tell the user to sign
704
+ * with the connected wallet, rather than deriving a key bound to the wrong
705
+ * wallet — which would silently corrupt the encrypted baseline and later
706
+ * misreport as a stale baseline.
707
+ */
708
+ declare class WalletSignatureMismatchError extends Error {
709
+ readonly expectedWallet: string;
710
+ constructor(expectedWallet: string);
711
+ }
698
712
  /**
699
713
  * Derive an AES-256 key from the wallet's deterministic signature over
700
714
  * the domain-separated payload, via HKDF-SHA256.
@@ -816,11 +830,16 @@ declare function loadVerificationData(): Promise<StoredVerificationData | null>;
816
830
  * can't `signMessage` (no method on the adapter, e.g., older Ledger
817
831
  * firmware) OR the user cancelled the prompt OR the wallet erred. The
818
832
  * `detail` field carries the specific cause when present.
833
+ * - `wallet-mismatch`: the `signMessage` signature did not verify against
834
+ * the connected wallet's public key — a different wallet signed the
835
+ * prompt (commonly another extension set as the browser default). NOT a
836
+ * stale baseline: the on-chain blob is intact. UX should ask the user to
837
+ * sign with the connected wallet and must NOT offer a destructive reset.
819
838
  * - `stale-baseline`: blob predates a `reset_identity_state` cycle.
820
839
  * Treat as terminal recovery failure; route to fresh-capture flow.
821
840
  * - `unknown-error`: catch-all (RPC failure, malformed blob, etc.).
822
841
  */
823
- type BaselineRecoveryReason = "no-on-chain-identity" | "no-encrypted-baseline" | "signing-unavailable" | "stale-baseline" | "unknown-error";
842
+ type BaselineRecoveryReason = "no-on-chain-identity" | "no-encrypted-baseline" | "signing-unavailable" | "wallet-mismatch" | "stale-baseline" | "unknown-error";
824
843
  interface BaselineRecoveryResult {
825
844
  recovered: boolean;
826
845
  reason?: BaselineRecoveryReason;
@@ -951,4 +970,4 @@ declare function fetchChallenge(executorUrl: string, walletAddress: string, apiK
951
970
  */
952
971
  declare function encodeAudioAsBase64(samples: Float32Array): string;
953
972
 
954
- export { type AgentHumanOperator, type AudioCapture, type BaselineRecoveryReason, type BaselineRecoveryResult, type BaselineWallet, type CaptureOptions, type CaptureStage, type ChallengeResponse, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_MIN_DISTANCE, DEFAULT_THRESHOLD, ENCRYPTED_BASELINE_BLOB_BYTES, 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, StaleEncryptedBaselineError, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, TOUCH_FEATURE_COUNT, type TemporalFingerprint, type TouchSample, type VerificationResult, attestAgentOperator, bigintToBytes32, bytes32ToBigint, bytesToFingerprint, clearBaselineKeyCache, computeCommitment, decryptBaselineBlob, deriveBaselineKey, deriveEncryptedBaselinePda, encodeAudioAsBase64, encryptBaselineBlob, extractAccelerationMagnitude, extractMotionFeatures, extractMouseDynamics, extractSpeakerFeatures, extractSpeakerFeaturesDetailed, extractTouchFeatures, fetchChallenge, fetchEncryptedBaseline, fetchIdentityState, fingerprintToBytes, fuseFeatures, fuseRawFeatures, generateLissajousPoints, generateLissajousSequence, generatePhrase, generatePhraseSequence, generateProof, generateSalt, generateSolanaProof, generateTBH, getAgentHumanOperator, getOrDeriveBaselineKey, hammingDistance, loadVerificationData, packBits, prepareCircuitInput, randomLissajousParams, recoverBaselineFromChain, serializeProof, simhash, storeVerificationData, submitResetViaWallet, submitViaRelayer, submitViaWallet, toBigEndian32, verifyEntrosAttestation };
973
+ export { type AgentHumanOperator, type AudioCapture, type BaselineRecoveryReason, type BaselineRecoveryResult, type BaselineWallet, type CaptureOptions, type CaptureStage, type ChallengeResponse, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_MIN_DISTANCE, DEFAULT_THRESHOLD, ENCRYPTED_BASELINE_BLOB_BYTES, 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, StaleEncryptedBaselineError, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, TOUCH_FEATURE_COUNT, type TemporalFingerprint, type TouchSample, type VerificationResult, WalletSignatureMismatchError, attestAgentOperator, bigintToBytes32, bytes32ToBigint, bytesToFingerprint, clearBaselineKeyCache, computeCommitment, decryptBaselineBlob, deriveBaselineKey, deriveEncryptedBaselinePda, encodeAudioAsBase64, encryptBaselineBlob, extractAccelerationMagnitude, extractMotionFeatures, extractMouseDynamics, extractSpeakerFeatures, extractSpeakerFeaturesDetailed, extractTouchFeatures, fetchChallenge, fetchEncryptedBaseline, fetchIdentityState, fingerprintToBytes, fuseFeatures, fuseRawFeatures, generateLissajousPoints, generateLissajousSequence, generatePhrase, generatePhraseSequence, generateProof, generateSalt, generateSolanaProof, generateTBH, getAgentHumanOperator, getOrDeriveBaselineKey, hammingDistance, loadVerificationData, packBits, prepareCircuitInput, randomLissajousParams, recoverBaselineFromChain, serializeProof, simhash, storeVerificationData, submitResetViaWallet, submitViaRelayer, submitViaWallet, toBigEndian32, verifyEntrosAttestation };
package/dist/index.d.ts CHANGED
@@ -695,6 +695,20 @@ interface BaselineWallet {
695
695
  publicKey: PublicKey;
696
696
  signMessage: (msg: Uint8Array) => Promise<Uint8Array>;
697
697
  }
698
+ /**
699
+ * Thrown when a `signMessage` result fails to verify against the wallet's own
700
+ * public key — i.e. a different wallet signed than the one connected. The most
701
+ * common cause is a second wallet extension (e.g. Backpack) set as the browser
702
+ * default and intercepting the prompt while a different wallet (e.g. Phantom)
703
+ * is the one actually connected. Surfaced so callers can tell the user to sign
704
+ * with the connected wallet, rather than deriving a key bound to the wrong
705
+ * wallet — which would silently corrupt the encrypted baseline and later
706
+ * misreport as a stale baseline.
707
+ */
708
+ declare class WalletSignatureMismatchError extends Error {
709
+ readonly expectedWallet: string;
710
+ constructor(expectedWallet: string);
711
+ }
698
712
  /**
699
713
  * Derive an AES-256 key from the wallet's deterministic signature over
700
714
  * the domain-separated payload, via HKDF-SHA256.
@@ -816,11 +830,16 @@ declare function loadVerificationData(): Promise<StoredVerificationData | null>;
816
830
  * can't `signMessage` (no method on the adapter, e.g., older Ledger
817
831
  * firmware) OR the user cancelled the prompt OR the wallet erred. The
818
832
  * `detail` field carries the specific cause when present.
833
+ * - `wallet-mismatch`: the `signMessage` signature did not verify against
834
+ * the connected wallet's public key — a different wallet signed the
835
+ * prompt (commonly another extension set as the browser default). NOT a
836
+ * stale baseline: the on-chain blob is intact. UX should ask the user to
837
+ * sign with the connected wallet and must NOT offer a destructive reset.
819
838
  * - `stale-baseline`: blob predates a `reset_identity_state` cycle.
820
839
  * Treat as terminal recovery failure; route to fresh-capture flow.
821
840
  * - `unknown-error`: catch-all (RPC failure, malformed blob, etc.).
822
841
  */
823
- type BaselineRecoveryReason = "no-on-chain-identity" | "no-encrypted-baseline" | "signing-unavailable" | "stale-baseline" | "unknown-error";
842
+ type BaselineRecoveryReason = "no-on-chain-identity" | "no-encrypted-baseline" | "signing-unavailable" | "wallet-mismatch" | "stale-baseline" | "unknown-error";
824
843
  interface BaselineRecoveryResult {
825
844
  recovered: boolean;
826
845
  reason?: BaselineRecoveryReason;
@@ -951,4 +970,4 @@ declare function fetchChallenge(executorUrl: string, walletAddress: string, apiK
951
970
  */
952
971
  declare function encodeAudioAsBase64(samples: Float32Array): string;
953
972
 
954
- export { type AgentHumanOperator, type AudioCapture, type BaselineRecoveryReason, type BaselineRecoveryResult, type BaselineWallet, type CaptureOptions, type CaptureStage, type ChallengeResponse, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_MIN_DISTANCE, DEFAULT_THRESHOLD, ENCRYPTED_BASELINE_BLOB_BYTES, 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, StaleEncryptedBaselineError, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, TOUCH_FEATURE_COUNT, type TemporalFingerprint, type TouchSample, type VerificationResult, attestAgentOperator, bigintToBytes32, bytes32ToBigint, bytesToFingerprint, clearBaselineKeyCache, computeCommitment, decryptBaselineBlob, deriveBaselineKey, deriveEncryptedBaselinePda, encodeAudioAsBase64, encryptBaselineBlob, extractAccelerationMagnitude, extractMotionFeatures, extractMouseDynamics, extractSpeakerFeatures, extractSpeakerFeaturesDetailed, extractTouchFeatures, fetchChallenge, fetchEncryptedBaseline, fetchIdentityState, fingerprintToBytes, fuseFeatures, fuseRawFeatures, generateLissajousPoints, generateLissajousSequence, generatePhrase, generatePhraseSequence, generateProof, generateSalt, generateSolanaProof, generateTBH, getAgentHumanOperator, getOrDeriveBaselineKey, hammingDistance, loadVerificationData, packBits, prepareCircuitInput, randomLissajousParams, recoverBaselineFromChain, serializeProof, simhash, storeVerificationData, submitResetViaWallet, submitViaRelayer, submitViaWallet, toBigEndian32, verifyEntrosAttestation };
973
+ export { type AgentHumanOperator, type AudioCapture, type BaselineRecoveryReason, type BaselineRecoveryResult, type BaselineWallet, type CaptureOptions, type CaptureStage, type ChallengeResponse, type CircuitInput, DEFAULT_CAPTURE_MS, DEFAULT_MIN_DISTANCE, DEFAULT_THRESHOLD, ENCRYPTED_BASELINE_BLOB_BYTES, 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, StaleEncryptedBaselineError, type StatsSummary, type StoredVerificationData, type SubmissionResult, type TBH, TOUCH_FEATURE_COUNT, type TemporalFingerprint, type TouchSample, type VerificationResult, WalletSignatureMismatchError, attestAgentOperator, bigintToBytes32, bytes32ToBigint, bytesToFingerprint, clearBaselineKeyCache, computeCommitment, decryptBaselineBlob, deriveBaselineKey, deriveEncryptedBaselinePda, encodeAudioAsBase64, encryptBaselineBlob, extractAccelerationMagnitude, extractMotionFeatures, extractMouseDynamics, extractSpeakerFeatures, extractSpeakerFeaturesDetailed, extractTouchFeatures, fetchChallenge, fetchEncryptedBaseline, fetchIdentityState, fingerprintToBytes, fuseFeatures, fuseRawFeatures, generateLissajousPoints, generateLissajousSequence, generatePhrase, generatePhraseSequence, generateProof, generateSalt, generateSolanaProof, generateTBH, getAgentHumanOperator, getOrDeriveBaselineKey, hammingDistance, loadVerificationData, packBits, prepareCircuitInput, randomLissajousParams, recoverBaselineFromChain, serializeProof, simhash, storeVerificationData, submitResetViaWallet, submitViaRelayer, submitViaWallet, toBigEndian32, verifyEntrosAttestation };
package/dist/index.js CHANGED
@@ -47,6 +47,7 @@ __export(index_exports, {
47
47
  SPEAKER_FEATURE_COUNT: () => SPEAKER_FEATURE_COUNT,
48
48
  StaleEncryptedBaselineError: () => StaleEncryptedBaselineError,
49
49
  TOUCH_FEATURE_COUNT: () => TOUCH_FEATURE_COUNT,
50
+ WalletSignatureMismatchError: () => WalletSignatureMismatchError,
50
51
  attestAgentOperator: () => attestAgentOperator,
51
52
  bigintToBytes32: () => bigintToBytes32,
52
53
  bytes32ToBigint: () => bytes32ToBigint,
@@ -4744,6 +4745,7 @@ async function buildEd25519ReceiptIx(receipt) {
4744
4745
  }
4745
4746
 
4746
4747
  // src/identity/baseline.ts
4748
+ var import_ed25519 = require("@noble/curves/ed25519");
4747
4749
  var BLOB_VERSION = 1;
4748
4750
  var ALGORITHM_AES_256_GCM = 1;
4749
4751
  var ENCRYPTED_BASELINE_BLOB_BYTES = 96;
@@ -4803,17 +4805,40 @@ async function deriveEncryptedBaselinePda(walletPubkey) {
4803
4805
  programId
4804
4806
  );
4805
4807
  }
4808
+ var WalletSignatureMismatchError = class extends Error {
4809
+ constructor(expectedWallet) {
4810
+ super(
4811
+ `signMessage did not verify against the connected wallet ${expectedWallet}. A different wallet likely signed the prompt \u2014 disable other wallet extensions or set your selected wallet as the default, then retry.`
4812
+ );
4813
+ this.name = "WalletSignatureMismatchError";
4814
+ this.expectedWallet = expectedWallet;
4815
+ }
4816
+ };
4806
4817
  async function deriveBaselineKey(wallet) {
4807
4818
  if (typeof wallet.signMessage !== "function") {
4808
4819
  throw new Error("wallet does not support signMessage");
4809
4820
  }
4810
4821
  const message = buildDomainMessage(wallet.publicKey);
4811
- const signature = await wallet.signMessage(new TextEncoder().encode(message));
4822
+ const messageBytes = new TextEncoder().encode(message);
4823
+ const signature = await wallet.signMessage(messageBytes);
4812
4824
  if (!(signature instanceof Uint8Array) || signature.length !== 64) {
4813
4825
  throw new Error(
4814
4826
  `expected 64-byte Ed25519 signature, got ${signature?.length ?? "non-Uint8Array"}`
4815
4827
  );
4816
4828
  }
4829
+ let signatureValid;
4830
+ try {
4831
+ signatureValid = import_ed25519.ed25519.verify(
4832
+ signature,
4833
+ messageBytes,
4834
+ wallet.publicKey.toBytes()
4835
+ );
4836
+ } catch {
4837
+ signatureValid = false;
4838
+ }
4839
+ if (!signatureValid) {
4840
+ throw new WalletSignatureMismatchError(wallet.publicKey.toBase58());
4841
+ }
4817
4842
  const ikm = await crypto.subtle.importKey(
4818
4843
  "raw",
4819
4844
  signature,
@@ -5555,19 +5580,19 @@ async function fetchIdentityState(walletPubkey, connection) {
5555
5580
  const accountInfo = await connection.getAccountInfo(identityPda);
5556
5581
  if (!accountInfo) return null;
5557
5582
  const coder = new anchor.BorshAccountsCoder(entros_anchor_default);
5558
- const decoded = coder.decode("identityState", accountInfo.data);
5583
+ const decoded = coder.decode("IdentityState", accountInfo.data);
5559
5584
  return {
5560
5585
  owner: decoded.owner.toBase58(),
5561
- creationTimestamp: decoded.creationTimestamp.toNumber(),
5562
- lastVerificationTimestamp: decoded.lastVerificationTimestamp.toNumber(),
5563
- verificationCount: decoded.verificationCount,
5564
- trustScore: decoded.trustScore,
5565
- currentCommitment: new Uint8Array(decoded.currentCommitment),
5586
+ creationTimestamp: decoded.creation_timestamp.toNumber(),
5587
+ lastVerificationTimestamp: decoded.last_verification_timestamp.toNumber(),
5588
+ verificationCount: decoded.verification_count,
5589
+ trustScore: decoded.trust_score,
5590
+ currentCommitment: new Uint8Array(decoded.current_commitment),
5566
5591
  mint: decoded.mint.toBase58(),
5567
5592
  // Anchor's Borsh coder returns the raw BN for i64 fields; .toNumber()
5568
5593
  // is safe here because Unix timestamps fit in Number.MAX_SAFE_INTEGER
5569
5594
  // until year 275760.
5570
- lastResetTimestamp: decoded.lastResetTimestamp?.toNumber?.() ?? 0
5595
+ lastResetTimestamp: decoded.last_reset_timestamp?.toNumber?.() ?? 0
5571
5596
  };
5572
5597
  } catch {
5573
5598
  return null;
@@ -5669,6 +5694,13 @@ async function recoverBaselineFromChain(wallet, connection) {
5669
5694
  key = await getOrDeriveBaselineKey(wallet);
5670
5695
  } catch (err) {
5671
5696
  const detail = err instanceof Error ? err.message : String(err);
5697
+ if (err instanceof WalletSignatureMismatchError) {
5698
+ return {
5699
+ recovered: false,
5700
+ reason: "wallet-mismatch",
5701
+ detail
5702
+ };
5703
+ }
5672
5704
  return {
5673
5705
  recovered: false,
5674
5706
  reason: "signing-unavailable",
@@ -5948,6 +5980,7 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5948
5980
  } else {
5949
5981
  isFirstVerification = !previousData;
5950
5982
  }
5983
+ let walletMismatch = false;
5951
5984
  if (!isFirstVerification && !previousData && wallet && connection) {
5952
5985
  const baselineWallet = resolveBaselineWallet(wallet);
5953
5986
  if (baselineWallet) {
@@ -5957,6 +5990,7 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5957
5990
  previousData = await loadVerificationData();
5958
5991
  sdkLog("[Entros SDK] On-chain encrypted baseline recovered");
5959
5992
  } else {
5993
+ walletMismatch = recovery.reason === "wallet-mismatch";
5960
5994
  sdkLog(
5961
5995
  `[Entros SDK] On-chain encrypted baseline recovery not available (${recovery.reason ?? "unknown"})`
5962
5996
  );
@@ -5964,6 +5998,14 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5964
5998
  }
5965
5999
  }
5966
6000
  if (!isFirstVerification && !previousData) {
6001
+ if (walletMismatch) {
6002
+ return {
6003
+ success: false,
6004
+ commitment: tbh.commitmentBytes,
6005
+ isFirstVerification: false,
6006
+ error: "A different wallet signed than the one connected. Another wallet extension likely intercepted the signature prompt. Sign with your connected wallet, or disable other wallet extensions (or unset their default), then try again."
6007
+ };
6008
+ }
5967
6009
  return {
5968
6010
  success: false,
5969
6011
  commitment: tbh.commitmentBytes,
@@ -7051,6 +7093,7 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
7051
7093
  SPEAKER_FEATURE_COUNT,
7052
7094
  StaleEncryptedBaselineError,
7053
7095
  TOUCH_FEATURE_COUNT,
7096
+ WalletSignatureMismatchError,
7054
7097
  attestAgentOperator,
7055
7098
  bigintToBytes32,
7056
7099
  bytes32ToBigint,