@entros/pulse-sdk 3.1.0 → 3.3.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.js CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  DEFAULT_CAPTURE_MS: () => DEFAULT_CAPTURE_MS,
34
34
  DEFAULT_MIN_DISTANCE: () => DEFAULT_MIN_DISTANCE,
35
35
  DEFAULT_THRESHOLD: () => DEFAULT_THRESHOLD,
36
+ ENCRYPTED_BASELINE_BLOB_BYTES: () => ENCRYPTED_BASELINE_BLOB_BYTES,
36
37
  FINGERPRINT_BITS: () => FINGERPRINT_BITS,
37
38
  MAX_CAPTURE_MS: () => MAX_CAPTURE_MS,
38
39
  MIN_AUDIO_SAMPLES: () => MIN_AUDIO_SAMPLES,
@@ -44,14 +45,19 @@ __export(index_exports, {
44
45
  PulseSDK: () => PulseSDK,
45
46
  PulseSession: () => PulseSession,
46
47
  SPEAKER_FEATURE_COUNT: () => SPEAKER_FEATURE_COUNT,
48
+ StaleEncryptedBaselineError: () => StaleEncryptedBaselineError,
47
49
  TOUCH_FEATURE_COUNT: () => TOUCH_FEATURE_COUNT,
48
50
  attestAgentOperator: () => attestAgentOperator,
49
- autocorrelation: () => autocorrelation,
50
51
  bigintToBytes32: () => bigintToBytes32,
52
+ bytes32ToBigint: () => bytes32ToBigint,
53
+ bytesToFingerprint: () => bytesToFingerprint,
54
+ clearBaselineKeyCache: () => clearBaselineKeyCache,
51
55
  computeCommitment: () => computeCommitment,
52
- condense: () => condense,
56
+ decryptBaselineBlob: () => decryptBaselineBlob,
57
+ deriveBaselineKey: () => deriveBaselineKey,
58
+ deriveEncryptedBaselinePda: () => deriveEncryptedBaselinePda,
53
59
  encodeAudioAsBase64: () => encodeAudioAsBase64,
54
- entropy: () => entropy,
60
+ encryptBaselineBlob: () => encryptBaselineBlob,
55
61
  extractAccelerationMagnitude: () => extractAccelerationMagnitude,
56
62
  extractMotionFeatures: () => extractMotionFeatures,
57
63
  extractMouseDynamics: () => extractMouseDynamics,
@@ -59,7 +65,9 @@ __export(index_exports, {
59
65
  extractSpeakerFeaturesDetailed: () => extractSpeakerFeaturesDetailed,
60
66
  extractTouchFeatures: () => extractTouchFeatures,
61
67
  fetchChallenge: () => fetchChallenge,
68
+ fetchEncryptedBaseline: () => fetchEncryptedBaseline,
62
69
  fetchIdentityState: () => fetchIdentityState,
70
+ fingerprintToBytes: () => fingerprintToBytes,
63
71
  fuseFeatures: () => fuseFeatures,
64
72
  fuseRawFeatures: () => fuseRawFeatures,
65
73
  generateLissajousPoints: () => generateLissajousPoints,
@@ -71,22 +79,20 @@ __export(index_exports, {
71
79
  generateSolanaProof: () => generateSolanaProof,
72
80
  generateTBH: () => generateTBH,
73
81
  getAgentHumanOperator: () => getAgentHumanOperator,
82
+ getOrDeriveBaselineKey: () => getOrDeriveBaselineKey,
74
83
  hammingDistance: () => hammingDistance,
75
- kurtosis: () => kurtosis,
76
84
  loadVerificationData: () => loadVerificationData,
77
- mean: () => mean,
78
85
  packBits: () => packBits,
79
86
  prepareCircuitInput: () => prepareCircuitInput,
80
87
  randomLissajousParams: () => randomLissajousParams,
88
+ recoverBaselineFromChain: () => recoverBaselineFromChain,
81
89
  serializeProof: () => serializeProof,
82
90
  simhash: () => simhash,
83
- skewness: () => skewness,
84
91
  storeVerificationData: () => storeVerificationData,
85
92
  submitResetViaWallet: () => submitResetViaWallet,
86
93
  submitViaRelayer: () => submitViaRelayer,
87
94
  submitViaWallet: () => submitViaWallet,
88
95
  toBigEndian32: () => toBigEndian32,
89
- variance: () => variance,
90
96
  verifyEntrosAttestation: () => verifyEntrosAttestation
91
97
  });
92
98
  module.exports = __toCommonJS(index_exports);
@@ -2249,12 +2255,6 @@ var entros_anchor_default = {
2249
2255
  spec: "0.1.0",
2250
2256
  description: "Non-transferable identity token for Entros Protocol"
2251
2257
  },
2252
- docs: [
2253
- "Mint account space for Token-2022 with NonTransferable extension.",
2254
- "Base mint = 82 bytes, account type = 1 byte, extension type (2) + length (2) = 4 bytes,",
2255
- "NonTransferable data = 0 bytes. Plus multisig padding from Token-2022.",
2256
- "We use a constant derived from the Token-2022 spec."
2257
- ],
2258
2258
  instructions: [
2259
2259
  {
2260
2260
  name: "authorize_new_wallet",
@@ -3236,6 +3236,132 @@ var entros_anchor_default = {
3236
3236
  }
3237
3237
  ]
3238
3238
  },
3239
+ {
3240
+ name: "set_encrypted_baseline",
3241
+ docs: [
3242
+ "Write or overwrite the caller's encrypted baseline blob.",
3243
+ "",
3244
+ "The blob is opaque to the program \u2014 AES-256-GCM ciphertext of the",
3245
+ "user's previous SimHash plus salt, produced off-chain in the SDK",
3246
+ "under a key derived from a deterministic `signMessage`. The GCM",
3247
+ "auth tag binds the blob to (wallet, this PDA's address, on-chain",
3248
+ "`current_commitment` at encryption time), so a stale blob produced",
3249
+ "before a `reset_identity_state` fails authentication under the new",
3250
+ "commitment and the SDK falls back to a fresh-capture flow.",
3251
+ "",
3252
+ "The program never decrypts the blob \u2014 it only stores opaque bytes.",
3253
+ "Plaintext biometric data never reaches chain at any point.",
3254
+ "",
3255
+ "Guards:",
3256
+ "* Signer must equal the wallet that seeds the EncryptedBaseline",
3257
+ "PDA (enforced by the seeds constraint on the Accounts struct).",
3258
+ '* The caller\'s IdentityState PDA at `[b"identity", signer]` must',
3259
+ "already exist \u2014 pre-mint attempts are rejected with",
3260
+ "`IdentityStateNotFound`. Anchor's seeds constraint validates the",
3261
+ "PDA address; the `data_len() > 0` check confirms initialization."
3262
+ ],
3263
+ discriminator: [
3264
+ 10,
3265
+ 73,
3266
+ 41,
3267
+ 36,
3268
+ 2,
3269
+ 145,
3270
+ 87,
3271
+ 111
3272
+ ],
3273
+ accounts: [
3274
+ {
3275
+ name: "authority",
3276
+ writable: true,
3277
+ signer: true
3278
+ },
3279
+ {
3280
+ name: "identity_state",
3281
+ docs: [
3282
+ "UncheckedAccount because we only need to verify the IdentityState",
3283
+ "PDA exists at the expected address \u2014 we don't need to deserialize",
3284
+ "its fields. The seeds constraint validates the PDA address."
3285
+ ],
3286
+ pda: {
3287
+ seeds: [
3288
+ {
3289
+ kind: "const",
3290
+ value: [
3291
+ 105,
3292
+ 100,
3293
+ 101,
3294
+ 110,
3295
+ 116,
3296
+ 105,
3297
+ 116,
3298
+ 121
3299
+ ]
3300
+ },
3301
+ {
3302
+ kind: "account",
3303
+ path: "authority"
3304
+ }
3305
+ ]
3306
+ }
3307
+ },
3308
+ {
3309
+ name: "encrypted_baseline",
3310
+ docs: [
3311
+ "The EncryptedBaseline PDA. Created on first call, overwritten on",
3312
+ "subsequent calls. PDA seeds bind to the signer's wallet, so only",
3313
+ "the wallet owner can write to their own baseline."
3314
+ ],
3315
+ writable: true,
3316
+ pda: {
3317
+ seeds: [
3318
+ {
3319
+ kind: "const",
3320
+ value: [
3321
+ 101,
3322
+ 110,
3323
+ 99,
3324
+ 114,
3325
+ 121,
3326
+ 112,
3327
+ 116,
3328
+ 101,
3329
+ 100,
3330
+ 95,
3331
+ 98,
3332
+ 97,
3333
+ 115,
3334
+ 101,
3335
+ 108,
3336
+ 105,
3337
+ 110,
3338
+ 101
3339
+ ]
3340
+ },
3341
+ {
3342
+ kind: "account",
3343
+ path: "authority"
3344
+ }
3345
+ ]
3346
+ }
3347
+ },
3348
+ {
3349
+ name: "system_program",
3350
+ address: "11111111111111111111111111111111"
3351
+ }
3352
+ ],
3353
+ args: [
3354
+ {
3355
+ name: "blob",
3356
+ type: {
3357
+ array: [
3358
+ "u8",
3359
+ 96
3360
+ ]
3361
+ }
3362
+ }
3363
+ ]
3364
+ },
3239
3365
  {
3240
3366
  name: "update_anchor",
3241
3367
  docs: [
@@ -3539,6 +3665,19 @@ var entros_anchor_default = {
3539
3665
  }
3540
3666
  ],
3541
3667
  accounts: [
3668
+ {
3669
+ name: "EncryptedBaseline",
3670
+ discriminator: [
3671
+ 235,
3672
+ 60,
3673
+ 246,
3674
+ 174,
3675
+ 131,
3676
+ 9,
3677
+ 248,
3678
+ 146
3679
+ ]
3680
+ },
3542
3681
  {
3543
3682
  name: "IdentityState",
3544
3683
  discriminator: [
@@ -3593,6 +3732,19 @@ var entros_anchor_default = {
3593
3732
  59
3594
3733
  ]
3595
3734
  },
3735
+ {
3736
+ name: "EncryptedBaselineSet",
3737
+ discriminator: [
3738
+ 198,
3739
+ 176,
3740
+ 56,
3741
+ 167,
3742
+ 74,
3743
+ 225,
3744
+ 138,
3745
+ 121
3746
+ ]
3747
+ },
3596
3748
  {
3597
3749
  name: "MigrateIdentityEvent",
3598
3750
  discriminator: [
@@ -3717,6 +3869,11 @@ var entros_anchor_default = {
3717
3869
  code: 6021,
3718
3870
  name: "MalformedReceiptMessage",
3719
3871
  msg: "Receipt message has malformed length or layout"
3872
+ },
3873
+ {
3874
+ code: 6022,
3875
+ name: "IdentityStateNotFound",
3876
+ msg: "set_encrypted_baseline called before mint_anchor \u2014 IdentityState PDA does not exist"
3720
3877
  }
3721
3878
  ],
3722
3879
  types: [
@@ -3799,6 +3956,64 @@ var entros_anchor_default = {
3799
3956
  ]
3800
3957
  }
3801
3958
  },
3959
+ {
3960
+ name: "EncryptedBaseline",
3961
+ docs: [
3962
+ "Wallet-keyed encrypted baseline blob, stored at PDA seeds",
3963
+ '`[b"encrypted_baseline", wallet.key().as_ref()]`. Persists the user\'s',
3964
+ "previous SimHash + salt across cache wipes and device changes so the",
3965
+ "Hamming-distance ZK proof can recover its private witnesses on any",
3966
+ "device with the originating wallet.",
3967
+ "",
3968
+ "The blob is opaque ciphertext to the program \u2014 AES-256-GCM produced",
3969
+ "off-chain in the SDK under a key derived from a deterministic",
3970
+ "`signMessage` on a domain-separated payload. The GCM AAD binds the",
3971
+ "blob to (wallet, this PDA's address, current on-chain commitment),",
3972
+ "so a stale blob (post-`reset_identity_state`) fails authentication",
3973
+ "against the new commitment and the SDK falls back to a fresh-capture",
3974
+ "flow.",
3975
+ "",
3976
+ "The program never decrypts the blob \u2014 it only stores opaque bytes.",
3977
+ "Plaintext biometric data never reaches chain at any point."
3978
+ ],
3979
+ type: {
3980
+ kind: "struct",
3981
+ fields: [
3982
+ {
3983
+ name: "blob",
3984
+ docs: [
3985
+ "96-byte versioned ciphertext envelope.",
3986
+ "Layout: version(1) || algo(1) || reserved(2) || iv(12) || ct+tag(80)."
3987
+ ],
3988
+ type: {
3989
+ array: [
3990
+ "u8",
3991
+ 96
3992
+ ]
3993
+ }
3994
+ },
3995
+ {
3996
+ name: "bump",
3997
+ docs: [
3998
+ "PDA bump seed."
3999
+ ],
4000
+ type: "u8"
4001
+ }
4002
+ ]
4003
+ }
4004
+ },
4005
+ {
4006
+ name: "EncryptedBaselineSet",
4007
+ type: {
4008
+ kind: "struct",
4009
+ fields: [
4010
+ {
4011
+ name: "owner",
4012
+ type: "pubkey"
4013
+ }
4014
+ ]
4015
+ }
4016
+ },
3802
4017
  {
3803
4018
  name: "IdentityState",
3804
4019
  type: {
@@ -4528,7 +4743,250 @@ async function buildEd25519ReceiptIx(receipt) {
4528
4743
  });
4529
4744
  }
4530
4745
 
4746
+ // src/identity/baseline.ts
4747
+ var BLOB_VERSION = 1;
4748
+ var ALGORITHM_AES_256_GCM = 1;
4749
+ var ENCRYPTED_BASELINE_BLOB_BYTES = 96;
4750
+ var IV_BYTES = 12;
4751
+ var HEADER_BYTES = 4;
4752
+ var PLAINTEXT_BYTES = 64;
4753
+ function fingerprintToBytes(bits) {
4754
+ if (bits.length !== 256) {
4755
+ throw new Error(`expected 256-bit fingerprint, got ${bits.length}`);
4756
+ }
4757
+ const out = new Uint8Array(32);
4758
+ for (let i = 0; i < 256; i++) {
4759
+ if (bits[i] === 1) {
4760
+ out[i >> 3] = (out[i >> 3] ?? 0) | 1 << (i & 7);
4761
+ }
4762
+ }
4763
+ return out;
4764
+ }
4765
+ function bytesToFingerprint(bytes) {
4766
+ if (bytes.length !== 32) {
4767
+ throw new Error(`expected 32 bytes, got ${bytes.length}`);
4768
+ }
4769
+ const bits = new Array(256);
4770
+ for (let i = 0; i < 256; i++) {
4771
+ bits[i] = (bytes[i >> 3] ?? 0) >> (i & 7) & 1;
4772
+ }
4773
+ return bits;
4774
+ }
4775
+ function bytes32ToBigint(bytes) {
4776
+ if (bytes.length !== 32) {
4777
+ throw new Error(`expected 32 bytes, got ${bytes.length}`);
4778
+ }
4779
+ let val = BigInt(0);
4780
+ for (let i = 0; i < 32; i++) {
4781
+ val = val << BigInt(8) | BigInt(bytes[i] ?? 0);
4782
+ }
4783
+ return val;
4784
+ }
4785
+ function buildDomainMessage(walletPubkey) {
4786
+ return [
4787
+ "Entros Protocol \u2014 Identity Baseline Key Derivation",
4788
+ "",
4789
+ "By signing this message, you authorize Entros Protocol to derive",
4790
+ "an encryption key for your on-chain identity baseline. This is",
4791
+ "not a transaction.",
4792
+ "",
4793
+ `Wallet: ${walletPubkey.toBase58()}`,
4794
+ "Version: 1",
4795
+ "Domain: entros.io"
4796
+ ].join("\n");
4797
+ }
4798
+ async function deriveEncryptedBaselinePda(walletPubkey) {
4799
+ const { PublicKey: PK } = await import("@solana/web3.js");
4800
+ const programId = new PK(PROGRAM_IDS.entrosAnchor);
4801
+ return PK.findProgramAddressSync(
4802
+ [new TextEncoder().encode("encrypted_baseline"), walletPubkey.toBuffer()],
4803
+ programId
4804
+ );
4805
+ }
4806
+ async function deriveBaselineKey(wallet) {
4807
+ if (typeof wallet.signMessage !== "function") {
4808
+ throw new Error("wallet does not support signMessage");
4809
+ }
4810
+ const message = buildDomainMessage(wallet.publicKey);
4811
+ const signature = await wallet.signMessage(new TextEncoder().encode(message));
4812
+ if (!(signature instanceof Uint8Array) || signature.length !== 64) {
4813
+ throw new Error(
4814
+ `expected 64-byte Ed25519 signature, got ${signature?.length ?? "non-Uint8Array"}`
4815
+ );
4816
+ }
4817
+ const ikm = await crypto.subtle.importKey(
4818
+ "raw",
4819
+ signature,
4820
+ "HKDF",
4821
+ false,
4822
+ ["deriveBits"]
4823
+ );
4824
+ const bits = await crypto.subtle.deriveBits(
4825
+ {
4826
+ name: "HKDF",
4827
+ hash: "SHA-256",
4828
+ salt: wallet.publicKey.toBytes(),
4829
+ info: new TextEncoder().encode("entros-protocol/identity-baseline/v1")
4830
+ },
4831
+ ikm,
4832
+ 256
4833
+ );
4834
+ return crypto.subtle.importKey(
4835
+ "raw",
4836
+ bits,
4837
+ { name: "AES-GCM" },
4838
+ false,
4839
+ ["encrypt", "decrypt"]
4840
+ );
4841
+ }
4842
+ var baselineKeyCache = /* @__PURE__ */ new Map();
4843
+ async function getOrDeriveBaselineKey(wallet) {
4844
+ const pubkeyBase58 = wallet.publicKey.toBase58();
4845
+ const cached = baselineKeyCache.get(pubkeyBase58);
4846
+ if (cached) return cached;
4847
+ const pending = (async () => {
4848
+ try {
4849
+ return await deriveBaselineKey(wallet);
4850
+ } catch (err) {
4851
+ baselineKeyCache.delete(pubkeyBase58);
4852
+ throw err;
4853
+ }
4854
+ })();
4855
+ baselineKeyCache.set(pubkeyBase58, pending);
4856
+ return pending;
4857
+ }
4858
+ function clearBaselineKeyCache() {
4859
+ baselineKeyCache.clear();
4860
+ }
4861
+ function buildAAD(walletPubkey, baselinePda, commitment) {
4862
+ if (commitment.length !== 32) {
4863
+ throw new Error(`commitment must be 32 bytes, got ${commitment.length}`);
4864
+ }
4865
+ const aad = new Uint8Array(98);
4866
+ aad[0] = BLOB_VERSION;
4867
+ aad[1] = ALGORITHM_AES_256_GCM;
4868
+ aad.set(walletPubkey.toBytes(), 2);
4869
+ aad.set(baselinePda.toBytes(), 34);
4870
+ aad.set(commitment, 66);
4871
+ return aad;
4872
+ }
4873
+ async function encryptBaselineBlob(simhash2, salt, key, walletPubkey, baselinePda, commitment) {
4874
+ if (simhash2.length !== 32) {
4875
+ throw new Error(`simhash must be 32 bytes, got ${simhash2.length}`);
4876
+ }
4877
+ if (salt.length !== 32) {
4878
+ throw new Error(`salt must be 32 bytes, got ${salt.length}`);
4879
+ }
4880
+ const plaintext = new Uint8Array(PLAINTEXT_BYTES);
4881
+ plaintext.set(simhash2, 0);
4882
+ plaintext.set(salt, 32);
4883
+ const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
4884
+ const aad = buildAAD(walletPubkey, baselinePda, commitment);
4885
+ const ciphertextWithTag = new Uint8Array(
4886
+ await crypto.subtle.encrypt(
4887
+ {
4888
+ name: "AES-GCM",
4889
+ iv,
4890
+ additionalData: aad
4891
+ },
4892
+ key,
4893
+ plaintext
4894
+ )
4895
+ );
4896
+ const blob = new Uint8Array(ENCRYPTED_BASELINE_BLOB_BYTES);
4897
+ blob[0] = BLOB_VERSION;
4898
+ blob[1] = ALGORITHM_AES_256_GCM;
4899
+ blob.set(iv, HEADER_BYTES);
4900
+ blob.set(ciphertextWithTag, HEADER_BYTES + IV_BYTES);
4901
+ return blob;
4902
+ }
4903
+ var StaleEncryptedBaselineError = class extends Error {
4904
+ constructor(message) {
4905
+ super(message);
4906
+ this.name = "StaleEncryptedBaselineError";
4907
+ }
4908
+ };
4909
+ async function decryptBaselineBlob(blob, key, walletPubkey, baselinePda, commitment) {
4910
+ if (commitment.length !== 32) {
4911
+ throw new Error(`commitment must be 32 bytes, got ${commitment.length}`);
4912
+ }
4913
+ if (blob.length !== ENCRYPTED_BASELINE_BLOB_BYTES) {
4914
+ throw new Error(
4915
+ `blob must be ${ENCRYPTED_BASELINE_BLOB_BYTES} bytes, got ${blob.length}`
4916
+ );
4917
+ }
4918
+ if (blob[0] !== BLOB_VERSION) {
4919
+ throw new Error(`unsupported blob version: ${blob[0]}`);
4920
+ }
4921
+ if (blob[1] !== ALGORITHM_AES_256_GCM) {
4922
+ throw new Error(`unsupported algorithm id: ${blob[1]}`);
4923
+ }
4924
+ const iv = blob.slice(HEADER_BYTES, HEADER_BYTES + IV_BYTES);
4925
+ const ciphertextWithTag = blob.slice(
4926
+ HEADER_BYTES + IV_BYTES,
4927
+ ENCRYPTED_BASELINE_BLOB_BYTES
4928
+ );
4929
+ const aad = buildAAD(walletPubkey, baselinePda, commitment);
4930
+ let plaintext;
4931
+ try {
4932
+ plaintext = await crypto.subtle.decrypt(
4933
+ {
4934
+ name: "AES-GCM",
4935
+ iv,
4936
+ additionalData: aad
4937
+ },
4938
+ key,
4939
+ ciphertextWithTag
4940
+ );
4941
+ } catch (err) {
4942
+ if (err instanceof Error && err.name === "OperationError") {
4943
+ throw new StaleEncryptedBaselineError(
4944
+ "encrypted baseline auth-tag verification failed \u2014 blob is stale or AAD does not match"
4945
+ );
4946
+ }
4947
+ throw err;
4948
+ }
4949
+ const bytes = new Uint8Array(plaintext);
4950
+ return {
4951
+ simhash: bytes.slice(0, 32),
4952
+ salt: bytes.slice(32, 64)
4953
+ };
4954
+ }
4955
+ async function fetchEncryptedBaseline(walletPubkey, connection) {
4956
+ const [baselinePda] = await deriveEncryptedBaselinePda(walletPubkey);
4957
+ const accountInfo = await connection.getAccountInfo(baselinePda);
4958
+ if (!accountInfo) return null;
4959
+ const raw = accountInfo.data instanceof Uint8Array ? accountInfo.data : new Uint8Array(accountInfo.data);
4960
+ if (raw.length < 8 + ENCRYPTED_BASELINE_BLOB_BYTES + 1) {
4961
+ return null;
4962
+ }
4963
+ return raw.slice(8, 8 + ENCRYPTED_BASELINE_BLOB_BYTES);
4964
+ }
4965
+
4531
4966
  // src/submit/wallet.ts
4967
+ async function buildSetEncryptedBaselineIx(anchorProgram, walletPubkey, blob) {
4968
+ if (blob.length !== ENCRYPTED_BASELINE_BLOB_BYTES) {
4969
+ throw new Error(
4970
+ `encrypted baseline blob must be ${ENCRYPTED_BASELINE_BLOB_BYTES} bytes, got ${blob.length}`
4971
+ );
4972
+ }
4973
+ const { PublicKey, SystemProgram } = await import("@solana/web3.js");
4974
+ const programId = new PublicKey(PROGRAM_IDS.entrosAnchor);
4975
+ const [identityPda] = PublicKey.findProgramAddressSync(
4976
+ [new TextEncoder().encode("identity"), walletPubkey.toBuffer()],
4977
+ programId
4978
+ );
4979
+ const [encryptedBaselinePda] = PublicKey.findProgramAddressSync(
4980
+ [new TextEncoder().encode("encrypted_baseline"), walletPubkey.toBuffer()],
4981
+ programId
4982
+ );
4983
+ return anchorProgram.methods.setEncryptedBaseline(Array.from(blob)).accounts({
4984
+ authority: walletPubkey,
4985
+ identityState: identityPda,
4986
+ encryptedBaseline: encryptedBaselinePda,
4987
+ systemProgram: SystemProgram.programId
4988
+ }).instruction();
4989
+ }
4532
4990
  async function confirmAndCheck(connection, signature) {
4533
4991
  if (!signature) {
4534
4992
  throw new Error("confirmAndCheck called without a transaction signature");
@@ -4715,10 +5173,19 @@ async function submitViaWallet(proof, commitment, options) {
4715
5173
  systemProgram: SystemProgram.programId
4716
5174
  }).instruction();
4717
5175
  const tx = new Transaction();
4718
- tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 25e4 }));
5176
+ const computeUnitLimit = options.encryptedBaselineBlob ? 3e5 : 25e4;
5177
+ tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }));
4719
5178
  tx.add(createChallengeIx);
4720
5179
  tx.add(verifyProofIx);
4721
5180
  tx.add(updateAnchorIx);
5181
+ if (options.encryptedBaselineBlob) {
5182
+ const setBaselineIx = await buildSetEncryptedBaselineIx(
5183
+ anchorProgram,
5184
+ provider.wallet.publicKey,
5185
+ options.encryptedBaselineBlob
5186
+ );
5187
+ tx.add(setBaselineIx);
5188
+ }
4722
5189
  tx.feePayer = provider.wallet.publicKey;
4723
5190
  tx.recentBlockhash = (await options.connection.getLatestBlockhash("confirmed")).blockhash;
4724
5191
  txSig = await options.wallet.sendTransaction(tx, options.connection, {
@@ -4794,9 +5261,18 @@ async function submitViaWallet(proof, commitment, options) {
4794
5261
  );
4795
5262
  }
4796
5263
  const tx = new Transaction();
4797
- tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 }));
5264
+ const computeUnitLimit = options.encryptedBaselineBlob ? 25e4 : 2e5;
5265
+ tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }));
4798
5266
  if (ed25519Ix) tx.add(ed25519Ix);
4799
5267
  tx.add(mintAnchorIx);
5268
+ if (options.encryptedBaselineBlob) {
5269
+ const setBaselineIx = await buildSetEncryptedBaselineIx(
5270
+ anchorProgram,
5271
+ provider.wallet.publicKey,
5272
+ options.encryptedBaselineBlob
5273
+ );
5274
+ tx.add(setBaselineIx);
5275
+ }
4800
5276
  tx.feePayer = provider.wallet.publicKey;
4801
5277
  tx.recentBlockhash = (await options.connection.getLatestBlockhash("confirmed")).blockhash;
4802
5278
  txSig = await options.wallet.sendTransaction(tx, options.connection, {
@@ -4851,8 +5327,16 @@ async function submitResetViaWallet(commitment, options) {
4851
5327
  systemProgram: SystemProgram.programId
4852
5328
  }).instruction();
4853
5329
  const tx = new Transaction();
4854
- tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 15e4 }));
5330
+ tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 }));
4855
5331
  tx.add(resetIx);
5332
+ if (options.encryptedBaselineBlob) {
5333
+ const setBaselineIx = await buildSetEncryptedBaselineIx(
5334
+ anchorProgram,
5335
+ provider.wallet.publicKey,
5336
+ options.encryptedBaselineBlob
5337
+ );
5338
+ tx.add(setBaselineIx);
5339
+ }
4856
5340
  tx.feePayer = provider.wallet.publicKey;
4857
5341
  tx.recentBlockhash = (await options.connection.getLatestBlockhash("confirmed")).blockhash;
4858
5342
  const txSig = await options.wallet.sendTransaction(
@@ -5167,6 +5651,65 @@ async function loadVerificationData() {
5167
5651
  return inMemoryStore;
5168
5652
  }
5169
5653
  }
5654
+ async function recoverBaselineFromChain(wallet, connection) {
5655
+ try {
5656
+ const identity = await fetchIdentityState(
5657
+ wallet.publicKey.toBase58(),
5658
+ connection
5659
+ );
5660
+ if (!identity) {
5661
+ return { recovered: false, reason: "no-on-chain-identity" };
5662
+ }
5663
+ const blob = await fetchEncryptedBaseline(wallet.publicKey, connection);
5664
+ if (!blob) {
5665
+ return { recovered: false, reason: "no-encrypted-baseline" };
5666
+ }
5667
+ let key;
5668
+ try {
5669
+ key = await getOrDeriveBaselineKey(wallet);
5670
+ } catch (err) {
5671
+ const detail = err instanceof Error ? err.message : String(err);
5672
+ return {
5673
+ recovered: false,
5674
+ reason: "signing-unavailable",
5675
+ detail
5676
+ };
5677
+ }
5678
+ const [baselinePda] = await deriveEncryptedBaselinePda(wallet.publicKey);
5679
+ let plaintext;
5680
+ try {
5681
+ plaintext = await decryptBaselineBlob(
5682
+ blob,
5683
+ key,
5684
+ wallet.publicKey,
5685
+ baselinePda,
5686
+ identity.currentCommitment
5687
+ );
5688
+ } catch (err) {
5689
+ if (err instanceof StaleEncryptedBaselineError) {
5690
+ return { recovered: false, reason: "stale-baseline" };
5691
+ }
5692
+ const detail = err instanceof Error ? err.message : String(err);
5693
+ return { recovered: false, reason: "unknown-error", detail };
5694
+ }
5695
+ const fingerprint = bytesToFingerprint(plaintext.simhash);
5696
+ const saltBigint = bytes32ToBigint(plaintext.salt);
5697
+ const commitmentBigint = bytes32ToBigint(identity.currentCommitment);
5698
+ await storeVerificationData({
5699
+ fingerprint,
5700
+ salt: saltBigint.toString(),
5701
+ commitment: commitmentBigint.toString(),
5702
+ timestamp: identity.lastVerificationTimestamp > 0 ? identity.lastVerificationTimestamp * 1e3 : Date.now()
5703
+ });
5704
+ sdkLog(
5705
+ "[Entros SDK] Recovered local baseline from on-chain EncryptedBaseline PDA"
5706
+ );
5707
+ return { recovered: true };
5708
+ } catch (err) {
5709
+ const detail = err instanceof Error ? err.message : String(err);
5710
+ return { recovered: false, reason: "unknown-error", detail };
5711
+ }
5712
+ }
5170
5713
 
5171
5714
  // src/pulse.ts
5172
5715
  async function extractFeatures(data) {
@@ -5277,6 +5820,41 @@ async function extractFingerprintAndValidate(sensorData, config, walletAddress,
5277
5820
  }
5278
5821
  return { ok: true, features, f0Contour, accelMagnitude, fingerprint, tbh, signedReceipt };
5279
5822
  }
5823
+ function resolveBaselineWallet(wallet) {
5824
+ if (!wallet) return null;
5825
+ const adapter = wallet.adapter ?? wallet;
5826
+ if (!adapter?.publicKey || typeof adapter.signMessage !== "function") {
5827
+ return null;
5828
+ }
5829
+ return {
5830
+ publicKey: adapter.publicKey,
5831
+ signMessage: adapter.signMessage.bind(adapter)
5832
+ };
5833
+ }
5834
+ async function buildEncryptedBaselineBlobBestEffort(wallet, fingerprint, salt, commitmentBytes) {
5835
+ const baselineWallet = resolveBaselineWallet(wallet);
5836
+ if (!baselineWallet) return void 0;
5837
+ try {
5838
+ const key = await getOrDeriveBaselineKey(baselineWallet);
5839
+ const [baselinePda] = await deriveEncryptedBaselinePda(baselineWallet.publicKey);
5840
+ const simhashBytes = fingerprintToBytes(fingerprint);
5841
+ const saltBytes = bigintToBytes32(salt);
5842
+ return await encryptBaselineBlob(
5843
+ simhashBytes,
5844
+ saltBytes,
5845
+ key,
5846
+ baselineWallet.publicKey,
5847
+ baselinePda,
5848
+ commitmentBytes
5849
+ );
5850
+ } catch (err) {
5851
+ const msg = err instanceof Error ? err.message : String(err);
5852
+ sdkWarn(
5853
+ `[Entros SDK] Encrypted-baseline build skipped (cross-device recovery unavailable this session): ${msg}`
5854
+ );
5855
+ return void 0;
5856
+ }
5857
+ }
5280
5858
  async function processSensorData(sensorData, config, wallet, connection, onProgress) {
5281
5859
  const audioSamples = sensorData.audio?.samples.length ?? 0;
5282
5860
  const motionSamples = sensorData.motion.length;
@@ -5348,7 +5926,7 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5348
5926
  }
5349
5927
  const { fingerprint, tbh, features, signedReceipt } = extraction;
5350
5928
  let isFirstVerification;
5351
- const previousData = await loadVerificationData();
5929
+ let previousData = await loadVerificationData();
5352
5930
  if (wallet && connection) {
5353
5931
  const walletPubkey = wallet.adapter?.publicKey ?? wallet.publicKey;
5354
5932
  if (walletPubkey) {
@@ -5370,6 +5948,21 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5370
5948
  } else {
5371
5949
  isFirstVerification = !previousData;
5372
5950
  }
5951
+ if (!isFirstVerification && !previousData && wallet && connection) {
5952
+ const baselineWallet = resolveBaselineWallet(wallet);
5953
+ if (baselineWallet) {
5954
+ onProgress?.("Recovering baseline from chain...");
5955
+ const recovery = await recoverBaselineFromChain(baselineWallet, connection);
5956
+ if (recovery.recovered) {
5957
+ previousData = await loadVerificationData();
5958
+ sdkLog("[Entros SDK] On-chain encrypted baseline recovered");
5959
+ } else {
5960
+ sdkLog(
5961
+ `[Entros SDK] On-chain encrypted baseline recovery not available (${recovery.reason ?? "unknown"})`
5962
+ );
5963
+ }
5964
+ }
5965
+ }
5373
5966
  if (!isFirstVerification && !previousData) {
5374
5967
  return {
5375
5968
  success: false,
@@ -5435,6 +6028,12 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5435
6028
  onProgress?.("Submitting to Solana...");
5436
6029
  let submission;
5437
6030
  if (wallet && connection) {
6031
+ const encryptedBaselineBlob = await buildEncryptedBaselineBlobBestEffort(
6032
+ wallet,
6033
+ tbh.fingerprint,
6034
+ tbh.salt,
6035
+ tbh.commitmentBytes
6036
+ );
5438
6037
  if (isFirstVerification) {
5439
6038
  submission = await submitViaWallet(
5440
6039
  solanaProof ?? { proofBytes: new Uint8Array(0), publicInputs: [] },
@@ -5445,7 +6044,8 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5445
6044
  isFirstVerification: true,
5446
6045
  relayerUrl: config.relayerUrl,
5447
6046
  relayerApiKey: config.relayerApiKey,
5448
- signedReceipt
6047
+ signedReceipt,
6048
+ encryptedBaselineBlob
5449
6049
  }
5450
6050
  );
5451
6051
  } else {
@@ -5454,7 +6054,8 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5454
6054
  connection,
5455
6055
  isFirstVerification: false,
5456
6056
  relayerUrl: config.relayerUrl,
5457
- relayerApiKey: config.relayerApiKey
6057
+ relayerApiKey: config.relayerApiKey,
6058
+ encryptedBaselineBlob
5458
6059
  });
5459
6060
  }
5460
6061
  } else if (config.relayerUrl) {
@@ -5536,12 +6137,19 @@ async function processResetSensorData(sensorData, config, wallet, connection, on
5536
6137
  };
5537
6138
  }
5538
6139
  const { tbh } = extraction;
6140
+ const encryptedBaselineBlob = await buildEncryptedBaselineBlobBestEffort(
6141
+ wallet,
6142
+ tbh.fingerprint,
6143
+ tbh.salt,
6144
+ tbh.commitmentBytes
6145
+ );
5539
6146
  onProgress?.("Submitting reset to Solana...");
5540
6147
  const submission = await submitResetViaWallet(tbh.commitmentBytes, {
5541
6148
  wallet,
5542
6149
  connection,
5543
6150
  relayerUrl: config.relayerUrl,
5544
- relayerApiKey: config.relayerApiKey
6151
+ relayerApiKey: config.relayerApiKey,
6152
+ encryptedBaselineBlob
5545
6153
  });
5546
6154
  if (submission.success) {
5547
6155
  try {
@@ -6429,6 +7037,7 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
6429
7037
  DEFAULT_CAPTURE_MS,
6430
7038
  DEFAULT_MIN_DISTANCE,
6431
7039
  DEFAULT_THRESHOLD,
7040
+ ENCRYPTED_BASELINE_BLOB_BYTES,
6432
7041
  FINGERPRINT_BITS,
6433
7042
  MAX_CAPTURE_MS,
6434
7043
  MIN_AUDIO_SAMPLES,
@@ -6440,14 +7049,19 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
6440
7049
  PulseSDK,
6441
7050
  PulseSession,
6442
7051
  SPEAKER_FEATURE_COUNT,
7052
+ StaleEncryptedBaselineError,
6443
7053
  TOUCH_FEATURE_COUNT,
6444
7054
  attestAgentOperator,
6445
- autocorrelation,
6446
7055
  bigintToBytes32,
7056
+ bytes32ToBigint,
7057
+ bytesToFingerprint,
7058
+ clearBaselineKeyCache,
6447
7059
  computeCommitment,
6448
- condense,
7060
+ decryptBaselineBlob,
7061
+ deriveBaselineKey,
7062
+ deriveEncryptedBaselinePda,
6449
7063
  encodeAudioAsBase64,
6450
- entropy,
7064
+ encryptBaselineBlob,
6451
7065
  extractAccelerationMagnitude,
6452
7066
  extractMotionFeatures,
6453
7067
  extractMouseDynamics,
@@ -6455,7 +7069,9 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
6455
7069
  extractSpeakerFeaturesDetailed,
6456
7070
  extractTouchFeatures,
6457
7071
  fetchChallenge,
7072
+ fetchEncryptedBaseline,
6458
7073
  fetchIdentityState,
7074
+ fingerprintToBytes,
6459
7075
  fuseFeatures,
6460
7076
  fuseRawFeatures,
6461
7077
  generateLissajousPoints,
@@ -6467,22 +7083,20 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
6467
7083
  generateSolanaProof,
6468
7084
  generateTBH,
6469
7085
  getAgentHumanOperator,
7086
+ getOrDeriveBaselineKey,
6470
7087
  hammingDistance,
6471
- kurtosis,
6472
7088
  loadVerificationData,
6473
- mean,
6474
7089
  packBits,
6475
7090
  prepareCircuitInput,
6476
7091
  randomLissajousParams,
7092
+ recoverBaselineFromChain,
6477
7093
  serializeProof,
6478
7094
  simhash,
6479
- skewness,
6480
7095
  storeVerificationData,
6481
7096
  submitResetViaWallet,
6482
7097
  submitViaRelayer,
6483
7098
  submitViaWallet,
6484
7099
  toBigEndian32,
6485
- variance,
6486
7100
  verifyEntrosAttestation
6487
7101
  });
6488
7102
  //# sourceMappingURL=index.js.map