@entros/pulse-sdk 3.2.0 → 3.3.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.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,11 +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
51
  bigintToBytes32: () => bigintToBytes32,
52
+ bytes32ToBigint: () => bytes32ToBigint,
53
+ bytesToFingerprint: () => bytesToFingerprint,
54
+ clearBaselineKeyCache: () => clearBaselineKeyCache,
50
55
  computeCommitment: () => computeCommitment,
56
+ decryptBaselineBlob: () => decryptBaselineBlob,
57
+ deriveBaselineKey: () => deriveBaselineKey,
58
+ deriveEncryptedBaselinePda: () => deriveEncryptedBaselinePda,
51
59
  encodeAudioAsBase64: () => encodeAudioAsBase64,
60
+ encryptBaselineBlob: () => encryptBaselineBlob,
52
61
  extractAccelerationMagnitude: () => extractAccelerationMagnitude,
53
62
  extractMotionFeatures: () => extractMotionFeatures,
54
63
  extractMouseDynamics: () => extractMouseDynamics,
@@ -56,7 +65,9 @@ __export(index_exports, {
56
65
  extractSpeakerFeaturesDetailed: () => extractSpeakerFeaturesDetailed,
57
66
  extractTouchFeatures: () => extractTouchFeatures,
58
67
  fetchChallenge: () => fetchChallenge,
68
+ fetchEncryptedBaseline: () => fetchEncryptedBaseline,
59
69
  fetchIdentityState: () => fetchIdentityState,
70
+ fingerprintToBytes: () => fingerprintToBytes,
60
71
  fuseFeatures: () => fuseFeatures,
61
72
  fuseRawFeatures: () => fuseRawFeatures,
62
73
  generateLissajousPoints: () => generateLissajousPoints,
@@ -68,11 +79,13 @@ __export(index_exports, {
68
79
  generateSolanaProof: () => generateSolanaProof,
69
80
  generateTBH: () => generateTBH,
70
81
  getAgentHumanOperator: () => getAgentHumanOperator,
82
+ getOrDeriveBaselineKey: () => getOrDeriveBaselineKey,
71
83
  hammingDistance: () => hammingDistance,
72
84
  loadVerificationData: () => loadVerificationData,
73
85
  packBits: () => packBits,
74
86
  prepareCircuitInput: () => prepareCircuitInput,
75
87
  randomLissajousParams: () => randomLissajousParams,
88
+ recoverBaselineFromChain: () => recoverBaselineFromChain,
76
89
  serializeProof: () => serializeProof,
77
90
  simhash: () => simhash,
78
91
  storeVerificationData: () => storeVerificationData,
@@ -2242,12 +2255,6 @@ var entros_anchor_default = {
2242
2255
  spec: "0.1.0",
2243
2256
  description: "Non-transferable identity token for Entros Protocol"
2244
2257
  },
2245
- docs: [
2246
- "Mint account space for Token-2022 with NonTransferable extension.",
2247
- "Base mint = 82 bytes, account type = 1 byte, extension type (2) + length (2) = 4 bytes,",
2248
- "NonTransferable data = 0 bytes. Plus multisig padding from Token-2022.",
2249
- "We use a constant derived from the Token-2022 spec."
2250
- ],
2251
2258
  instructions: [
2252
2259
  {
2253
2260
  name: "authorize_new_wallet",
@@ -3229,6 +3236,132 @@ var entros_anchor_default = {
3229
3236
  }
3230
3237
  ]
3231
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
+ },
3232
3365
  {
3233
3366
  name: "update_anchor",
3234
3367
  docs: [
@@ -3532,6 +3665,19 @@ var entros_anchor_default = {
3532
3665
  }
3533
3666
  ],
3534
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
+ },
3535
3681
  {
3536
3682
  name: "IdentityState",
3537
3683
  discriminator: [
@@ -3586,6 +3732,19 @@ var entros_anchor_default = {
3586
3732
  59
3587
3733
  ]
3588
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
+ },
3589
3748
  {
3590
3749
  name: "MigrateIdentityEvent",
3591
3750
  discriminator: [
@@ -3710,6 +3869,11 @@ var entros_anchor_default = {
3710
3869
  code: 6021,
3711
3870
  name: "MalformedReceiptMessage",
3712
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"
3713
3877
  }
3714
3878
  ],
3715
3879
  types: [
@@ -3792,6 +3956,64 @@ var entros_anchor_default = {
3792
3956
  ]
3793
3957
  }
3794
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
+ },
3795
4017
  {
3796
4018
  name: "IdentityState",
3797
4019
  type: {
@@ -4521,7 +4743,250 @@ async function buildEd25519ReceiptIx(receipt) {
4521
4743
  });
4522
4744
  }
4523
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
+
4524
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
+ }
4525
4990
  async function confirmAndCheck(connection, signature) {
4526
4991
  if (!signature) {
4527
4992
  throw new Error("confirmAndCheck called without a transaction signature");
@@ -4708,10 +5173,19 @@ async function submitViaWallet(proof, commitment, options) {
4708
5173
  systemProgram: SystemProgram.programId
4709
5174
  }).instruction();
4710
5175
  const tx = new Transaction();
4711
- tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 25e4 }));
5176
+ const computeUnitLimit = options.encryptedBaselineBlob ? 3e5 : 25e4;
5177
+ tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }));
4712
5178
  tx.add(createChallengeIx);
4713
5179
  tx.add(verifyProofIx);
4714
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
+ }
4715
5189
  tx.feePayer = provider.wallet.publicKey;
4716
5190
  tx.recentBlockhash = (await options.connection.getLatestBlockhash("confirmed")).blockhash;
4717
5191
  txSig = await options.wallet.sendTransaction(tx, options.connection, {
@@ -4787,9 +5261,18 @@ async function submitViaWallet(proof, commitment, options) {
4787
5261
  );
4788
5262
  }
4789
5263
  const tx = new Transaction();
4790
- tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 }));
5264
+ const computeUnitLimit = options.encryptedBaselineBlob ? 25e4 : 2e5;
5265
+ tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }));
4791
5266
  if (ed25519Ix) tx.add(ed25519Ix);
4792
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
+ }
4793
5276
  tx.feePayer = provider.wallet.publicKey;
4794
5277
  tx.recentBlockhash = (await options.connection.getLatestBlockhash("confirmed")).blockhash;
4795
5278
  txSig = await options.wallet.sendTransaction(tx, options.connection, {
@@ -4844,8 +5327,16 @@ async function submitResetViaWallet(commitment, options) {
4844
5327
  systemProgram: SystemProgram.programId
4845
5328
  }).instruction();
4846
5329
  const tx = new Transaction();
4847
- tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 15e4 }));
5330
+ tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 }));
4848
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
+ }
4849
5340
  tx.feePayer = provider.wallet.publicKey;
4850
5341
  tx.recentBlockhash = (await options.connection.getLatestBlockhash("confirmed")).blockhash;
4851
5342
  const txSig = await options.wallet.sendTransaction(
@@ -5064,19 +5555,19 @@ async function fetchIdentityState(walletPubkey, connection) {
5064
5555
  const accountInfo = await connection.getAccountInfo(identityPda);
5065
5556
  if (!accountInfo) return null;
5066
5557
  const coder = new anchor.BorshAccountsCoder(entros_anchor_default);
5067
- const decoded = coder.decode("identityState", accountInfo.data);
5558
+ const decoded = coder.decode("IdentityState", accountInfo.data);
5068
5559
  return {
5069
5560
  owner: decoded.owner.toBase58(),
5070
- creationTimestamp: decoded.creationTimestamp.toNumber(),
5071
- lastVerificationTimestamp: decoded.lastVerificationTimestamp.toNumber(),
5072
- verificationCount: decoded.verificationCount,
5073
- trustScore: decoded.trustScore,
5074
- currentCommitment: new Uint8Array(decoded.currentCommitment),
5561
+ creationTimestamp: decoded.creation_timestamp.toNumber(),
5562
+ lastVerificationTimestamp: decoded.last_verification_timestamp.toNumber(),
5563
+ verificationCount: decoded.verification_count,
5564
+ trustScore: decoded.trust_score,
5565
+ currentCommitment: new Uint8Array(decoded.current_commitment),
5075
5566
  mint: decoded.mint.toBase58(),
5076
5567
  // Anchor's Borsh coder returns the raw BN for i64 fields; .toNumber()
5077
5568
  // is safe here because Unix timestamps fit in Number.MAX_SAFE_INTEGER
5078
5569
  // until year 275760.
5079
- lastResetTimestamp: decoded.lastResetTimestamp?.toNumber?.() ?? 0
5570
+ lastResetTimestamp: decoded.last_reset_timestamp?.toNumber?.() ?? 0
5080
5571
  };
5081
5572
  } catch {
5082
5573
  return null;
@@ -5160,6 +5651,65 @@ async function loadVerificationData() {
5160
5651
  return inMemoryStore;
5161
5652
  }
5162
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
+ }
5163
5713
 
5164
5714
  // src/pulse.ts
5165
5715
  async function extractFeatures(data) {
@@ -5270,6 +5820,41 @@ async function extractFingerprintAndValidate(sensorData, config, walletAddress,
5270
5820
  }
5271
5821
  return { ok: true, features, f0Contour, accelMagnitude, fingerprint, tbh, signedReceipt };
5272
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
+ }
5273
5858
  async function processSensorData(sensorData, config, wallet, connection, onProgress) {
5274
5859
  const audioSamples = sensorData.audio?.samples.length ?? 0;
5275
5860
  const motionSamples = sensorData.motion.length;
@@ -5341,7 +5926,7 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5341
5926
  }
5342
5927
  const { fingerprint, tbh, features, signedReceipt } = extraction;
5343
5928
  let isFirstVerification;
5344
- const previousData = await loadVerificationData();
5929
+ let previousData = await loadVerificationData();
5345
5930
  if (wallet && connection) {
5346
5931
  const walletPubkey = wallet.adapter?.publicKey ?? wallet.publicKey;
5347
5932
  if (walletPubkey) {
@@ -5363,6 +5948,21 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5363
5948
  } else {
5364
5949
  isFirstVerification = !previousData;
5365
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
+ }
5366
5966
  if (!isFirstVerification && !previousData) {
5367
5967
  return {
5368
5968
  success: false,
@@ -5428,6 +6028,12 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5428
6028
  onProgress?.("Submitting to Solana...");
5429
6029
  let submission;
5430
6030
  if (wallet && connection) {
6031
+ const encryptedBaselineBlob = await buildEncryptedBaselineBlobBestEffort(
6032
+ wallet,
6033
+ tbh.fingerprint,
6034
+ tbh.salt,
6035
+ tbh.commitmentBytes
6036
+ );
5431
6037
  if (isFirstVerification) {
5432
6038
  submission = await submitViaWallet(
5433
6039
  solanaProof ?? { proofBytes: new Uint8Array(0), publicInputs: [] },
@@ -5438,7 +6044,8 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5438
6044
  isFirstVerification: true,
5439
6045
  relayerUrl: config.relayerUrl,
5440
6046
  relayerApiKey: config.relayerApiKey,
5441
- signedReceipt
6047
+ signedReceipt,
6048
+ encryptedBaselineBlob
5442
6049
  }
5443
6050
  );
5444
6051
  } else {
@@ -5447,7 +6054,8 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
5447
6054
  connection,
5448
6055
  isFirstVerification: false,
5449
6056
  relayerUrl: config.relayerUrl,
5450
- relayerApiKey: config.relayerApiKey
6057
+ relayerApiKey: config.relayerApiKey,
6058
+ encryptedBaselineBlob
5451
6059
  });
5452
6060
  }
5453
6061
  } else if (config.relayerUrl) {
@@ -5529,12 +6137,19 @@ async function processResetSensorData(sensorData, config, wallet, connection, on
5529
6137
  };
5530
6138
  }
5531
6139
  const { tbh } = extraction;
6140
+ const encryptedBaselineBlob = await buildEncryptedBaselineBlobBestEffort(
6141
+ wallet,
6142
+ tbh.fingerprint,
6143
+ tbh.salt,
6144
+ tbh.commitmentBytes
6145
+ );
5532
6146
  onProgress?.("Submitting reset to Solana...");
5533
6147
  const submission = await submitResetViaWallet(tbh.commitmentBytes, {
5534
6148
  wallet,
5535
6149
  connection,
5536
6150
  relayerUrl: config.relayerUrl,
5537
- relayerApiKey: config.relayerApiKey
6151
+ relayerApiKey: config.relayerApiKey,
6152
+ encryptedBaselineBlob
5538
6153
  });
5539
6154
  if (submission.success) {
5540
6155
  try {
@@ -6422,6 +7037,7 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
6422
7037
  DEFAULT_CAPTURE_MS,
6423
7038
  DEFAULT_MIN_DISTANCE,
6424
7039
  DEFAULT_THRESHOLD,
7040
+ ENCRYPTED_BASELINE_BLOB_BYTES,
6425
7041
  FINGERPRINT_BITS,
6426
7042
  MAX_CAPTURE_MS,
6427
7043
  MIN_AUDIO_SAMPLES,
@@ -6433,11 +7049,19 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
6433
7049
  PulseSDK,
6434
7050
  PulseSession,
6435
7051
  SPEAKER_FEATURE_COUNT,
7052
+ StaleEncryptedBaselineError,
6436
7053
  TOUCH_FEATURE_COUNT,
6437
7054
  attestAgentOperator,
6438
7055
  bigintToBytes32,
7056
+ bytes32ToBigint,
7057
+ bytesToFingerprint,
7058
+ clearBaselineKeyCache,
6439
7059
  computeCommitment,
7060
+ decryptBaselineBlob,
7061
+ deriveBaselineKey,
7062
+ deriveEncryptedBaselinePda,
6440
7063
  encodeAudioAsBase64,
7064
+ encryptBaselineBlob,
6441
7065
  extractAccelerationMagnitude,
6442
7066
  extractMotionFeatures,
6443
7067
  extractMouseDynamics,
@@ -6445,7 +7069,9 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
6445
7069
  extractSpeakerFeaturesDetailed,
6446
7070
  extractTouchFeatures,
6447
7071
  fetchChallenge,
7072
+ fetchEncryptedBaseline,
6448
7073
  fetchIdentityState,
7074
+ fingerprintToBytes,
6449
7075
  fuseFeatures,
6450
7076
  fuseRawFeatures,
6451
7077
  generateLissajousPoints,
@@ -6457,11 +7083,13 @@ async function fetchChallenge(executorUrl, walletAddress, apiKey) {
6457
7083
  generateSolanaProof,
6458
7084
  generateTBH,
6459
7085
  getAgentHumanOperator,
7086
+ getOrDeriveBaselineKey,
6460
7087
  hammingDistance,
6461
7088
  loadVerificationData,
6462
7089
  packBits,
6463
7090
  prepareCircuitInput,
6464
7091
  randomLissajousParams,
7092
+ recoverBaselineFromChain,
6465
7093
  serializeProof,
6466
7094
  simhash,
6467
7095
  storeVerificationData,