@anuma/sdk 4.2.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,8 +13,21 @@ import { v7 as uuidv72 } from "uuid";
13
13
 
14
14
  // src/react/useEncryption.ts
15
15
  var SIGN_MESSAGE = "The app is asking you to sign this message to generate a key, which will be used to encrypt data.";
16
+ var EncryptionKeyMissingError = class extends Error {
17
+ constructor(address, version) {
18
+ super(
19
+ `Encryption key (${version}) not found for ${address}. Please sign a message to generate your encryption key.`
20
+ );
21
+ this.name = "EncryptionKeyMissingError";
22
+ this.address = address;
23
+ this.version = version;
24
+ }
25
+ };
16
26
  var encryptionKeyStore = /* @__PURE__ */ new Map();
17
27
  var pendingKeyRequests = /* @__PURE__ */ new Map();
28
+ var pendingCandidateDerives = /* @__PURE__ */ new Map();
29
+ var refreshCandidatesByAddress = /* @__PURE__ */ new Map();
30
+ var divergentDeriveAddresses = /* @__PURE__ */ new Set();
18
31
  var sessionEpoch = 0;
19
32
  var cryptoKeyCache = /* @__PURE__ */ new Map();
20
33
  var keyAvailableCallbacks = /* @__PURE__ */ new Map();
@@ -57,14 +70,11 @@ function notifyKeyAvailable(address) {
57
70
  }
58
71
  }
59
72
  var keyPairStore = /* @__PURE__ */ new Map();
60
- function getStoredKey(address) {
61
- const keys = encryptionKeyStore.get(address);
62
- return keys?.current ?? null;
63
- }
64
73
  function getStoredKeyByVersion(address, version) {
65
74
  const keys = encryptionKeyStore.get(address);
66
75
  if (!keys) return null;
67
- return version === "v2" ? keys.legacy : keys.current;
76
+ const value = version === "v2" ? keys.legacy : keys.current;
77
+ return value ?? null;
68
78
  }
69
79
  function setStoredKey(address, keys) {
70
80
  encryptionKeyStore.set(address, keys);
@@ -73,6 +83,8 @@ function clearEncryptionKey(address) {
73
83
  encryptionKeyStore.delete(address);
74
84
  cryptoKeyCache.delete(`${address}:v2`);
75
85
  cryptoKeyCache.delete(`${address}:v3`);
86
+ refreshCandidatesByAddress.delete(address);
87
+ divergentDeriveAddresses.delete(address);
76
88
  }
77
89
  function clearAllEncryptionState() {
78
90
  sessionEpoch += 1;
@@ -80,6 +92,9 @@ function clearAllEncryptionState() {
80
92
  cryptoKeyCache.clear();
81
93
  keyAvailableCallbacks.clear();
82
94
  pendingKeyRequests.clear();
95
+ pendingCandidateDerives.clear();
96
+ refreshCandidatesByAddress.clear();
97
+ divergentDeriveAddresses.clear();
83
98
  clearAllKeyPairs();
84
99
  for (const cb of clearAllEncryptionStateCallbacks) {
85
100
  try {
@@ -265,9 +280,7 @@ async function getEncryptionKey(address, version = "v3") {
265
280
  }
266
281
  const keyHex = getStoredKeyByVersion(address, version);
267
282
  if (!keyHex) {
268
- throw new Error(
269
- "Encryption key not found. Please sign a message to generate your encryption key."
270
- );
283
+ throw new EncryptionKeyMissingError(address, version);
271
284
  }
272
285
  const keyBytes = hexToBytes(keyHex);
273
286
  const cryptoKey = await crypto.subtle.importKey(
@@ -331,8 +344,43 @@ async function decryptDataBytesFromBytes(encrypted, address, version = "v3") {
331
344
  );
332
345
  return new Uint8Array(decryptedData);
333
346
  }
334
- function hasEncryptionKey(address) {
335
- return getStoredKey(address) !== null;
347
+ function hasEncryptionKey(address, version) {
348
+ return getStoredKeyByVersion(address, version ?? "v3") !== null;
349
+ }
350
+ function seedEncryptionKeys(address, keys) {
351
+ if (!isValidWalletAddress(address)) {
352
+ throw new Error(
353
+ `Invalid wallet address: ${address}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
354
+ );
355
+ }
356
+ if (!keys.legacy && !keys.current) {
357
+ throw new Error("seedEncryptionKeys: at least one of legacy or current is required");
358
+ }
359
+ if (keys.legacy !== void 0) {
360
+ assertAes256KeyHex("legacy", keys.legacy);
361
+ }
362
+ if (keys.current !== void 0) {
363
+ assertAes256KeyHex("current", keys.current);
364
+ }
365
+ const existing = encryptionKeyStore.get(address);
366
+ const next = {
367
+ legacy: keys.legacy ?? existing?.legacy,
368
+ current: keys.current ?? existing?.current
369
+ };
370
+ setStoredKey(address, next);
371
+ if (keys.legacy !== void 0) cryptoKeyCache.delete(`${address}:v2`);
372
+ if (keys.current !== void 0) cryptoKeyCache.delete(`${address}:v3`);
373
+ refreshCandidatesByAddress.delete(address);
374
+ divergentDeriveAddresses.delete(address);
375
+ notifyKeyAvailable(address);
376
+ }
377
+ var AES_256_KEY_HEX = /^[0-9a-fA-F]{64}$/;
378
+ function assertAes256KeyHex(field, value) {
379
+ if (!AES_256_KEY_HEX.test(value)) {
380
+ throw new Error(
381
+ `seedEncryptionKeys: ${field} must be 64 hex characters (32-byte AES key), got length ${value.length}`
382
+ );
383
+ }
336
384
  }
337
385
  async function encryptBytesWithKey(plaintext, key) {
338
386
  const plaintextBytes = typeof plaintext === "string" ? SHARED_TEXT_ENCODER.encode(plaintext) : plaintext;
@@ -381,20 +429,23 @@ async function decryptDataBatch(encryptedValues, address) {
381
429
  const key = await getEncryptionKey(address);
382
430
  return Promise.all(encryptedValues.map((value) => decryptDataWithKey(value, key)));
383
431
  }
384
- async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner) {
432
+ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner, options) {
385
433
  if (!isValidWalletAddress(walletAddress)) {
386
434
  throw new Error(
387
435
  `Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
388
436
  );
389
437
  }
390
- const existingKey = getStoredKey(walletAddress);
391
- if (existingKey) {
392
- return;
438
+ if (!options?.force && getStoredKeyByVersion(walletAddress, "v3") && getStoredKeyByVersion(walletAddress, "v2")) {
439
+ return true;
393
440
  }
394
- const pending = pendingKeyRequests.get(walletAddress);
395
- if (pending) {
396
- await pending;
397
- return;
441
+ if (!options?.force && divergentDeriveAddresses.has(walletAddress)) {
442
+ return false;
443
+ }
444
+ if (!options?.force) {
445
+ const pending = pendingKeyRequests.get(walletAddress);
446
+ if (pending) {
447
+ return pending;
448
+ }
398
449
  }
399
450
  const startEpoch = sessionEpoch;
400
451
  const promise = (async () => {
@@ -422,20 +473,143 @@ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSi
422
473
  deriveKeyFromSignatureV3(signature)
423
474
  ]);
424
475
  if (sessionEpoch !== startEpoch) {
425
- return;
476
+ return false;
426
477
  }
427
- setStoredKey(walletAddress, { legacy: legacyKey, current: currentKey });
478
+ const existing = encryptionKeyStore.get(walletAddress);
479
+ if (!options?.force && existing && (existing.legacy || existing.current)) {
480
+ const matchesCurrent = Boolean(existing.current && existing.current === currentKey);
481
+ const matchesLegacy = Boolean(existing.legacy && existing.legacy === legacyKey);
482
+ if (!matchesCurrent && !matchesLegacy) {
483
+ getLogger().warn(
484
+ "requestEncryptionKey: derived keys do not match existing store; leaving unchanged"
485
+ );
486
+ divergentDeriveAddresses.add(walletAddress);
487
+ return false;
488
+ }
489
+ setStoredKey(walletAddress, {
490
+ legacy: existing.legacy ?? legacyKey,
491
+ current: existing.current ?? currentKey
492
+ });
493
+ } else {
494
+ setStoredKey(walletAddress, { legacy: legacyKey, current: currentKey });
495
+ }
496
+ cryptoKeyCache.delete(`${walletAddress}:v2`);
497
+ cryptoKeyCache.delete(`${walletAddress}:v3`);
498
+ refreshCandidatesByAddress.delete(walletAddress);
499
+ divergentDeriveAddresses.delete(walletAddress);
428
500
  notifyKeyAvailable(walletAddress);
501
+ return true;
429
502
  })();
430
503
  pendingKeyRequests.set(walletAddress, promise);
431
504
  try {
432
- await promise;
505
+ return await promise;
433
506
  } finally {
434
507
  if (pendingKeyRequests.get(walletAddress) === promise) {
435
508
  pendingKeyRequests.delete(walletAddress);
436
509
  }
437
510
  }
438
511
  }
512
+ async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, signMessage, embeddedWalletSigner) {
513
+ if (!isValidWalletAddress(walletAddress)) {
514
+ throw new Error(
515
+ `Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
516
+ );
517
+ }
518
+ const parsed = parsePrefixedCiphertext(probeCiphertext);
519
+ if (!parsed) {
520
+ return false;
521
+ }
522
+ const cached = refreshCandidatesByAddress.get(walletAddress);
523
+ if (cached) {
524
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached);
525
+ }
526
+ let derivePromise = pendingCandidateDerives.get(walletAddress);
527
+ if (!derivePromise) {
528
+ derivePromise = (async () => {
529
+ const candidates2 = await deriveRefreshCandidates(signMessage, embeddedWalletSigner);
530
+ if (candidates2) {
531
+ refreshCandidatesByAddress.set(walletAddress, candidates2);
532
+ }
533
+ return candidates2;
534
+ })().finally(() => {
535
+ if (pendingCandidateDerives.get(walletAddress) === derivePromise) {
536
+ pendingCandidateDerives.delete(walletAddress);
537
+ }
538
+ });
539
+ pendingCandidateDerives.set(walletAddress, derivePromise);
540
+ }
541
+ const candidates = await derivePromise;
542
+ if (!candidates) {
543
+ return false;
544
+ }
545
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates);
546
+ }
547
+ function parsePrefixedCiphertext(probeCiphertext) {
548
+ const version = probeCiphertext.startsWith("enc:v3:") ? "v3" : probeCiphertext.startsWith("enc:v2:") ? "v2" : null;
549
+ if (!version) return null;
550
+ const encryptedData = probeCiphertext.slice(
551
+ version === "v3" ? "enc:v3:".length : "enc:v2:".length
552
+ );
553
+ if (encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(encryptedData)) {
554
+ return null;
555
+ }
556
+ return { version, encryptedData };
557
+ }
558
+ async function deriveRefreshCandidates(signMessage, embeddedWalletSigner) {
559
+ const startEpoch = sessionEpoch;
560
+ const signOptions = { showWalletUIs: false };
561
+ let signature;
562
+ try {
563
+ if (embeddedWalletSigner) {
564
+ signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
565
+ } else {
566
+ signature = await signMessage(SIGN_MESSAGE, signOptions);
567
+ }
568
+ } catch (error) {
569
+ if (embeddedWalletSigner && error instanceof Error) {
570
+ getLogger().warn(
571
+ "Embedded wallet signing failed during key refresh, falling back to standard signMessage:",
572
+ error.message
573
+ );
574
+ signature = await signMessage(SIGN_MESSAGE, signOptions);
575
+ } else {
576
+ throw error;
577
+ }
578
+ }
579
+ const [legacyKey, currentKey] = await Promise.all([
580
+ deriveKeyFromSignature(signature),
581
+ deriveKeyFromSignatureV3(signature)
582
+ ]);
583
+ if (sessionEpoch !== startEpoch) {
584
+ return null;
585
+ }
586
+ return { legacy: legacyKey, current: currentKey };
587
+ }
588
+ async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates) {
589
+ const candidateHex = parsed.version === "v2" ? candidates.legacy : candidates.current;
590
+ const keyBytes = hexToBytes(candidateHex);
591
+ const candidateCryptoKey = await crypto.subtle.importKey(
592
+ "raw",
593
+ keyBytes.buffer,
594
+ { name: "AES-GCM" },
595
+ false,
596
+ ["encrypt", "decrypt"]
597
+ );
598
+ try {
599
+ await decryptDataWithKey(parsed.encryptedData, candidateCryptoKey);
600
+ } catch {
601
+ getLogger().warn(
602
+ "refreshEncryptionKeyIfMatches: candidate keys failed to decrypt probe; leaving store unchanged"
603
+ );
604
+ return false;
605
+ }
606
+ setStoredKey(walletAddress, { legacy: candidates.legacy, current: candidates.current });
607
+ cryptoKeyCache.delete(`${walletAddress}:v2`);
608
+ cryptoKeyCache.delete(`${walletAddress}:v3`);
609
+ divergentDeriveAddresses.delete(walletAddress);
610
+ notifyKeyAvailable(walletAddress);
611
+ return true;
612
+ }
439
613
  var KEYPAIR_STORAGE_PREFIX = "ecdh_keypair_";
440
614
  async function persistKeyPair(address, startEpoch) {
441
615
  if (typeof window === "undefined") {
@@ -715,20 +889,29 @@ async function encryptField(value, address, signMessage, embeddedWalletSigner, s
715
889
  const encrypted = await encryptData(value, address);
716
890
  return `${ENCRYPTION_PREFIX}${encrypted}`;
717
891
  }
718
- async function decryptField(value, address) {
719
- if (!value) return value;
892
+ async function decryptFieldDetailed(value, address) {
893
+ if (!value) return { status: "plaintext", value };
720
894
  const detected = detectEncryptionVersion(value);
721
- if (!detected) return value;
895
+ if (!detected) return { status: "plaintext", value };
722
896
  if (detected.encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(detected.encryptedData)) {
723
- return value;
897
+ return { status: "invalid_payload", value, version: detected.version };
724
898
  }
725
899
  try {
726
- return await decryptData(detected.encryptedData, address, detected.version);
900
+ const plaintext = await decryptData(detected.encryptedData, address, detected.version);
901
+ return { status: "ok", value: plaintext, version: detected.version };
727
902
  } catch (error) {
728
- getLogger().warn("Failed to decrypt field, returning as-is:", error);
729
- return value;
903
+ if (error instanceof EncryptionKeyMissingError) {
904
+ getLogger().warn("Failed to decrypt field (key missing for version):", error.message);
905
+ return { status: "key_missing", value, version: detected.version };
906
+ }
907
+ getLogger().warn("Failed to decrypt field (auth mismatch or crypto error):", error);
908
+ return { status: "auth_mismatch", value, version: detected.version };
730
909
  }
731
910
  }
911
+ async function decryptField(value, address) {
912
+ const result = await decryptFieldDetailed(value, address);
913
+ return result.value;
914
+ }
732
915
  async function encryptJsonField(value, address, signMessage, embeddedWalletSigner, skipKeyRequest) {
733
916
  if (!value) return void 0;
734
917
  const jsonString = JSON.stringify(value);
@@ -828,20 +1011,40 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
828
1011
  throw error;
829
1012
  }
830
1013
  }
831
- async function decryptMaybeJsonField(value, address) {
832
- if (value === void 0) return void 0;
833
- if (value === null) return value;
1014
+ async function decryptMaybeJsonFieldDetailed(value, address) {
1015
+ if (value === void 0) return { value: void 0, status: "plaintext" };
1016
+ if (value === null) return { value, status: "plaintext" };
834
1017
  if (typeof value === "string") {
835
1018
  if (isEncrypted(value)) {
836
- return await decryptJsonField(value, address);
1019
+ const detailed = await decryptFieldDetailed(value, address);
1020
+ if (detailed.status !== "ok") {
1021
+ return { value: void 0, status: detailed.status, probe: value };
1022
+ }
1023
+ try {
1024
+ return { value: JSON.parse(detailed.value), status: "ok" };
1025
+ } catch (error) {
1026
+ getLogger().warn("Failed to parse decrypted JSON field:", error);
1027
+ return { value: void 0, status: "invalid_payload", probe: value };
1028
+ }
837
1029
  }
838
1030
  try {
839
- return JSON.parse(value);
1031
+ return { value: JSON.parse(value), status: "plaintext" };
840
1032
  } catch {
841
- return void 0;
1033
+ return { value: void 0, status: "plaintext" };
842
1034
  }
843
1035
  }
844
- return value;
1036
+ return { value, status: "plaintext" };
1037
+ }
1038
+ function worseDecryptStatus(a, b) {
1039
+ if (b === "ok" || b === "plaintext") return a;
1040
+ if (!a || a === "ok" || a === "plaintext") return b;
1041
+ const rank = (s) => {
1042
+ if (s === "auth_mismatch") return 3;
1043
+ if (s === "key_missing") return 2;
1044
+ if (s === "invalid_payload") return 1;
1045
+ return 0;
1046
+ };
1047
+ return rank(b) >= rank(a) ? b : a;
845
1048
  }
846
1049
  async function decryptMessageFields(message, address, signMessage, embeddedWalletSigner) {
847
1050
  if (!address) {
@@ -854,33 +1057,82 @@ async function decryptMessageFields(message, address, signMessage, embeddedWalle
854
1057
  getLogger().warn("Failed to request encryption key for decryption:", error);
855
1058
  }
856
1059
  }
857
- const decryptedContent = await decryptField(message.content, address);
858
- const decryptedThinking = message.thinking ? await decryptField(message.thinking, address) : message.thinking;
859
- const decryptedVector = await decryptMaybeJsonField(
1060
+ let assembled = await assembleDecryptedMessage(message, address);
1061
+ const needsRefresh = assembled.decryptionStatus === "auth_mismatch" || assembled.decryptionStatus === "key_missing";
1062
+ if (needsRefresh && signMessage && assembled.refreshProbe) {
1063
+ try {
1064
+ const refreshed = await refreshEncryptionKeyIfMatches(
1065
+ address,
1066
+ assembled.refreshProbe,
1067
+ signMessage,
1068
+ embeddedWalletSigner
1069
+ );
1070
+ if (refreshed) {
1071
+ assembled = await assembleDecryptedMessage(message, address);
1072
+ }
1073
+ } catch (error) {
1074
+ getLogger().warn("Failed to refresh encryption key after decrypt failure:", error);
1075
+ }
1076
+ }
1077
+ const { refreshProbe: _probe, ...result } = assembled;
1078
+ return result;
1079
+ }
1080
+ async function assembleDecryptedMessage(message, address) {
1081
+ const contentResult = await decryptFieldDetailed(message.content, address);
1082
+ const thinkingResult = message.thinking ? await decryptFieldDetailed(message.thinking, address) : null;
1083
+ const vectorResult = await decryptMaybeJsonFieldDetailed(
860
1084
  message.vector,
861
1085
  address
862
1086
  );
863
- const decryptedChunks = await decryptMaybeJsonField(
1087
+ const chunksResult = await decryptMaybeJsonFieldDetailed(
864
1088
  message.chunks,
865
1089
  address
866
1090
  );
867
- const decryptedSources = await decryptMaybeJsonField(
1091
+ const sourcesResult = await decryptMaybeJsonFieldDetailed(
868
1092
  message.sources,
869
1093
  address
870
1094
  );
871
- const decryptedThoughtProcess = await decryptMaybeJsonField(
1095
+ const thoughtProcessResult = await decryptMaybeJsonFieldDetailed(
872
1096
  message.thoughtProcess,
873
1097
  address
874
1098
  );
875
- return {
1099
+ let decryptionStatus;
1100
+ let refreshProbe;
1101
+ const consider = (status, probe) => {
1102
+ decryptionStatus = worseDecryptStatus(decryptionStatus, status);
1103
+ if (!refreshProbe && probe && (status === "auth_mismatch" || status === "key_missing")) {
1104
+ refreshProbe = probe;
1105
+ }
1106
+ };
1107
+ consider(contentResult.status, isEncrypted(message.content) ? message.content : void 0);
1108
+ if (thinkingResult && message.thinking && typeof message.thinking === "string") {
1109
+ consider(thinkingResult.status, isEncrypted(message.thinking) ? message.thinking : void 0);
1110
+ }
1111
+ if (vectorResult.probe) consider(vectorResult.status, vectorResult.probe);
1112
+ if (chunksResult.probe) consider(chunksResult.status, chunksResult.probe);
1113
+ if (sourcesResult.probe) consider(sourcesResult.status, sourcesResult.probe);
1114
+ if (thoughtProcessResult.probe) {
1115
+ consider(thoughtProcessResult.status, thoughtProcessResult.probe);
1116
+ }
1117
+ const statusForConsumer = decryptionStatus === "ok" || decryptionStatus === "plaintext" || !decryptionStatus ? void 0 : decryptionStatus;
1118
+ const result = {
876
1119
  ...message,
877
- content: decryptedContent,
878
- thinking: decryptedThinking,
879
- vector: decryptedVector,
880
- chunks: decryptedChunks,
881
- sources: decryptedSources,
882
- thoughtProcess: decryptedThoughtProcess
1120
+ content: contentResult.value,
1121
+ thinking: thinkingResult ? thinkingResult.value : message.thinking,
1122
+ vector: vectorResult.value,
1123
+ chunks: chunksResult.value,
1124
+ sources: sourcesResult.value,
1125
+ thoughtProcess: thoughtProcessResult.value
883
1126
  };
1127
+ if (statusForConsumer) {
1128
+ result.decryptionStatus = statusForConsumer;
1129
+ } else {
1130
+ delete result.decryptionStatus;
1131
+ }
1132
+ if (refreshProbe) {
1133
+ result.refreshProbe = refreshProbe;
1134
+ }
1135
+ return result;
884
1136
  }
885
1137
 
886
1138
  // src/lib/db/chat/types.ts
@@ -6442,7 +6694,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6442
6694
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
6443
6695
  );
6444
6696
  }
6445
- const { recall: recall2 } = await import("./recall-WXCZHXHZ.mjs");
6697
+ const { recall: recall2 } = await import("./recall-FPPCKYYH.mjs");
6446
6698
  let recallDegraded = [];
6447
6699
  const result = await recall2(
6448
6700
  query,
@@ -7382,6 +7634,7 @@ function toChunkMemory(r) {
7382
7634
 
7383
7635
  export {
7384
7636
  createSseClient,
7637
+ EncryptionKeyMissingError,
7385
7638
  onClearAllEncryptionState,
7386
7639
  onKeyAvailable,
7387
7640
  clearEncryptionKey,
@@ -7394,11 +7647,13 @@ export {
7394
7647
  decryptDataBytes,
7395
7648
  decryptDataBytesFromBytes,
7396
7649
  hasEncryptionKey,
7650
+ seedEncryptionKeys,
7397
7651
  encryptDataWithKey,
7398
7652
  decryptDataWithKey,
7399
7653
  encryptDataBatch,
7400
7654
  decryptDataBatch,
7401
7655
  requestEncryptionKey,
7656
+ refreshEncryptionKeyIfMatches,
7402
7657
  requestKeyPair,
7403
7658
  exportPublicKey,
7404
7659
  hasKeyPair,