@vaultkeepr/core 0.1.2 → 0.2.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
  EXTENSION_MIN_PAYLOAD_VERSION: () => EXTENSION_MIN_PAYLOAD_VERSION,
34
34
  FRAGMENTED_DEFAULT_THRESHOLD: () => FRAGMENTED_DEFAULT_THRESHOLD,
35
35
  FRAGMENTED_DEFAULT_TOTAL: () => FRAGMENTED_DEFAULT_TOTAL,
36
+ KeyJoinError: () => KeyJoinError,
36
37
  MOBILE_MAX_PAYLOAD_VERSION: () => MOBILE_MAX_PAYLOAD_VERSION,
37
38
  MOBILE_MIN_PAYLOAD_VERSION: () => MOBILE_MIN_PAYLOAD_VERSION,
38
39
  P256_SPKI_HEADER: () => P256_SPKI_HEADER,
@@ -43,11 +44,14 @@ __export(index_exports, {
43
44
  PAYLOAD_VERSION_V2: () => PAYLOAD_VERSION_V2,
44
45
  PAYLOAD_VERSION_V3: () => PAYLOAD_VERSION_V3,
45
46
  PAYLOAD_VERSION_V4: () => PAYLOAD_VERSION_V4,
47
+ RECOVERY_CODE_LENGTH: () => RECOVERY_CODE_LENGTH,
46
48
  TAG_COLORS: () => TAG_COLORS,
47
49
  VALID_TAGS: () => VALID_TAGS,
48
50
  VaultKeepR_SLM: () => VaultKeepR_SLM,
51
+ accountKeyState: () => accountKeyState,
49
52
  addEntry: () => addEntry,
50
53
  analyzeVaultHealth: () => analyzeVaultHealth,
54
+ argon2RecoveryKdf: () => argon2RecoveryKdf,
51
55
  buildAttestationObject: () => buildAttestationObject,
52
56
  buildAuthDataForCreate: () => buildAuthDataForCreate,
53
57
  buildAuthDataForGet: () => buildAuthDataForGet,
@@ -62,6 +66,7 @@ __export(index_exports, {
62
66
  captureError: () => captureError,
63
67
  checkEmailBreaches: () => checkEmailBreaches,
64
68
  checkVaultBreaches: () => checkVaultBreaches,
69
+ cleanEntryTitle: () => cleanEntryTitle,
65
70
  cleanMrzField: () => cleanMrzField,
66
71
  clearErrorBuffer: () => clearErrorBuffer,
67
72
  clearHiddenWalletCache: () => clearHiddenWalletCache,
@@ -72,8 +77,10 @@ __export(index_exports, {
72
77
  createExportPayload: () => createExportPayload,
73
78
  createFolder: () => createFolder,
74
79
  createKeyEnvelope: () => createKeyEnvelope,
80
+ createRecoveryBlob: () => createRecoveryBlob,
75
81
  createSecureShare: () => createSecureShare,
76
82
  createVaultPayload: () => createVaultPayload,
83
+ decideEncryptedImport: () => decideEncryptedImport,
77
84
  decompressVault: () => decompressVault,
78
85
  decryptFromPair: () => decryptFromPair,
79
86
  decryptKeyEnvelope: () => decryptKeyEnvelope,
@@ -95,6 +102,7 @@ __export(index_exports, {
95
102
  deriveMrzKseed: () => deriveMrzKseed,
96
103
  deriveNfcKey: () => deriveNfcKey,
97
104
  derivePairSecret: () => derivePairSecret,
105
+ deriveRecoveryId: () => deriveRecoveryId,
98
106
  digitsOnly: () => digitsOnly,
99
107
  encryptAndFragmentDocument: () => encryptAndFragmentDocument,
100
108
  encryptForPair: () => encryptForPair,
@@ -112,6 +120,7 @@ __export(index_exports, {
112
120
  formatCardDisplayMasked: () => formatCardDisplayMasked,
113
121
  formatCardNumber: () => formatCardNumber,
114
122
  formatErrorReport: () => formatErrorReport,
123
+ formatRecoveryCode: () => formatRecoveryCode,
115
124
  fromBase64Url: () => fromBase64Url,
116
125
  generateBlurredThumbnailCanvas: () => generateBlurredThumbnailCanvas,
117
126
  generateCredentialId: () => generateCredentialId,
@@ -123,17 +132,22 @@ __export(index_exports, {
123
132
  generatePassphrase: () => generatePassphrase,
124
133
  generatePassword: () => generatePassword,
125
134
  generatePrfSalt: () => generatePrfSalt,
135
+ generateRecoveryCode: () => generateRecoveryCode,
126
136
  generateSaltArgon2: () => generateSaltArgon2,
127
137
  generateSharePin: () => generateSharePin,
128
138
  getCardBrand: () => getCardBrand,
129
139
  getChecksumAddress: () => getChecksumAddress,
140
+ getEntryDisplayName: () => getEntryDisplayName,
130
141
  getEntryGroupLabel: () => getEntryGroupLabel,
131
142
  getErrorBuffer: () => getErrorBuffer,
132
143
  getHiddenWalletFromPassword: () => getHiddenWalletFromPassword,
144
+ getHiddenWalletFromSecretKey: () => getHiddenWalletFromSecretKey,
133
145
  getHiddenWalletLegacy: () => getHiddenWalletLegacy,
134
146
  getHiddenWalletPrivateKey: () => getHiddenWalletPrivateKey,
147
+ getHiddenWalletPrivateKeyFromSecretKey: () => getHiddenWalletPrivateKeyFromSecretKey,
135
148
  getLast4: () => getLast4,
136
149
  getMostUsedEntries: () => getMostUsedEntries,
150
+ getPwnedCountForHash: () => getPwnedCountForHash,
137
151
  getPwnedPasswordCount: () => getPwnedPasswordCount,
138
152
  getTOTPCode: () => getTOTPCode,
139
153
  getTOTPRemainingSeconds: () => getTOTPRemainingSeconds,
@@ -147,6 +161,7 @@ __export(index_exports, {
147
161
  importProtonPassJson: () => importProtonPassJson,
148
162
  importVaultText: () => importVaultText,
149
163
  isAcceptablePayloadVersion: () => isAcceptablePayloadVersion,
164
+ isAccountKeyText: () => isAccountKeyText,
150
165
  isCurrentPayloadVersion: () => isCurrentPayloadVersion,
151
166
  isEncryptedExport: () => isEncryptedExport,
152
167
  isNfcPayloadV1: () => isNfcPayloadV1,
@@ -158,15 +173,19 @@ __export(index_exports, {
158
173
  isPasskeyPrivateKeyEncrypted: () => isPasskeyPrivateKeyEncrypted,
159
174
  isPrfSupported: () => isPrfSupported,
160
175
  isProtonPassExport: () => isProtonPassExport,
176
+ isValidRecoveryCode: () => isValidRecoveryCode,
161
177
  isWebAuthnSupported: () => isWebAuthnSupported,
178
+ joinWithKeyCode: () => joinWithKeyCode,
162
179
  maskCardNumber: () => maskCardNumber,
163
180
  mergeFolderTrees: () => mergeFolderTrees,
164
181
  mergeFolders: () => mergeFolders,
165
182
  mergeFragments: () => mergeFragments,
166
183
  mergeImportedEntries: () => mergeImportedEntries,
167
184
  moveFolder: () => moveFolder,
185
+ normalizeRecoveryCode: () => normalizeRecoveryCode,
168
186
  normalizeSignatureForKdf: () => normalizeSignatureForKdf,
169
187
  normalizeUrl: () => normalizeUrl,
188
+ openRecoveryBlob: () => openRecoveryBlob,
170
189
  parseMRZ: () => parseMRZ,
171
190
  parseMrzDate: () => parseMrzDate,
172
191
  parsePairPayload: () => parsePairPayload,
@@ -176,6 +195,10 @@ __export(index_exports, {
176
195
  parseTOTPUri: () => parseTOTPUri,
177
196
  parseVault: () => parseVault,
178
197
  parseVaultPayload: () => parseVaultPayload,
198
+ passageIdentity: () => passageIdentity,
199
+ passageNext: () => passageNext,
200
+ pointerWalletFor: () => pointerWalletFor,
201
+ prepareKeyJoin: () => prepareKeyJoin,
179
202
  publicKeyToCose: () => publicKeyToCose,
180
203
  publicKeyToSpki: () => publicKeyToSpki,
181
204
  reassembleAndDecryptDocument: () => reassembleAndDecryptDocument,
@@ -239,6 +262,78 @@ function getEntryGroupLabel(folder) {
239
262
  if (!folder) return ENTRY_GROUP_LABELS.identifiants;
240
263
  return ENTRY_GROUP_LABELS[folder] ?? folder;
241
264
  }
265
+ var JUNK_SUBDOMAIN_PREFIXES = /* @__PURE__ */ new Set([
266
+ "www",
267
+ "app",
268
+ "mobile",
269
+ "accounts",
270
+ "dashboard",
271
+ "kyc",
272
+ "mail",
273
+ "web",
274
+ "my",
275
+ "login",
276
+ "portal",
277
+ "id",
278
+ "news"
279
+ ]);
280
+ function brandFrom(url) {
281
+ if (!url?.trim()) return null;
282
+ try {
283
+ const raw = url.includes("://") ? url : `https://${url}`;
284
+ const host = new URL(raw).hostname.replace(/^www\./i, "");
285
+ if (/^\d+(\.\d+){3}$/.test(host)) return null;
286
+ const parts = host.split(".").filter(Boolean);
287
+ if (parts.length < 2) return null;
288
+ let label = parts[0];
289
+ if (JUNK_SUBDOMAIN_PREFIXES.has(label.toLowerCase()) && parts.length > 2) label = parts[1];
290
+ if (!label) return null;
291
+ return { brand: label.charAt(0).toUpperCase() + label.slice(1).toLowerCase(), host: host.toLowerCase() };
292
+ } catch {
293
+ return null;
294
+ }
295
+ }
296
+ function cleanEntryTitle(rawTitle, url) {
297
+ const raw = rawTitle.trim();
298
+ let candidate = raw;
299
+ {
300
+ const lower = raw.toLowerCase();
301
+ let pos = lower.indexOf("passkey");
302
+ while (pos > 0) {
303
+ let i = pos;
304
+ while (i > 0 && /\s/.test(raw.charAt(i - 1))) i--;
305
+ const prefix = raw.slice(0, i);
306
+ if (i < pos && !prefix.includes("\n")) {
307
+ let j = pos + 7;
308
+ while (j < raw.length && /\s/.test(raw.charAt(j))) j++;
309
+ const tail = raw.slice(j);
310
+ if (j > pos + 7 && tail.length > 0 && !tail.includes("\n")) {
311
+ if (prefix.trim() !== "" && /\d/.test(tail)) candidate = prefix.trim();
312
+ break;
313
+ }
314
+ }
315
+ pos = lower.indexOf("passkey", pos + 1);
316
+ }
317
+ }
318
+ const brandInfo = brandFrom(url);
319
+ if (brandInfo) {
320
+ if (!candidate) return brandInfo.brand;
321
+ if (/^[\d\s()+.-]+$/.test(candidate)) return brandInfo.brand;
322
+ if (candidate.toLowerCase() === brandInfo.brand.toLowerCase()) return brandInfo.brand;
323
+ const schemeStripped = candidate.replace(/^https?:\/\//i, "");
324
+ const nl = schemeStripped.lastIndexOf("\n");
325
+ const slash = schemeStripped.indexOf("/", nl + 1);
326
+ const hostCandidate = slash === -1 ? schemeStripped : schemeStripped.slice(0, slash);
327
+ if (hostCandidate.toLowerCase() === brandInfo.host) {
328
+ return brandInfo.brand;
329
+ }
330
+ }
331
+ return candidate || brandInfo?.brand || raw;
332
+ }
333
+ function getEntryDisplayName(entry, primaryUrl) {
334
+ const url = primaryUrl ?? entry.urls?.[0]?.uri ?? entry.url ?? void 0;
335
+ return cleanEntryTitle(entry.title ?? entry.username ?? "", url);
336
+ }
242
337
  function resolveFolderName(folderId, folderTree) {
243
338
  if (!folderId) return ENTRY_GROUP_LABELS.identifiants;
244
339
  if (folderId in ENTRY_GROUP_LABELS) return ENTRY_GROUP_LABELS[folderId];
@@ -737,9 +832,12 @@ function normalizeUrl(raw) {
737
832
  let u = (raw ?? "").trim().toLowerCase();
738
833
  u = u.replace(/^https?:\/\//, "");
739
834
  u = u.replace(/^www\./, "");
740
- u = u.replace(/#.*$/, "");
741
- u = u.replace(/\/+$/, "");
742
- return u;
835
+ const nl = u.lastIndexOf("\n");
836
+ const hash = u.indexOf("#", nl + 1);
837
+ if (hash !== -1) u = u.slice(0, hash);
838
+ let end = u.length;
839
+ while (end > 0 && u.charAt(end - 1) === "/") end--;
840
+ return u.slice(0, end);
743
841
  }
744
842
  function entryFingerprint(entry) {
745
843
  const url = normalizeUrl(entry.url ?? "");
@@ -908,14 +1006,14 @@ function isEncryptedExport(jsonStr) {
908
1006
  return false;
909
1007
  }
910
1008
  }
911
- async function importEncryptedVault(encryptedJson, password, argon2Opts) {
1009
+ async function importEncryptedVault(encryptedJson, password, argon2Opts, precomputed) {
912
1010
  const payload = JSON.parse(encryptedJson.trim());
913
1011
  if (payload?.format !== ENCRYPTED_EXPORT_FORMAT || !payload.salt || !payload.nonce || !payload.ciphertext) {
914
1012
  throw new Error("Format d'export chiffr\xE9 invalide.");
915
1013
  }
916
1014
  const salt = (0, import_utils5.hexToBytes)(payload.salt);
917
1015
  const effectiveOpts = payload.kdf ? { t: payload.kdf.t, m: payload.kdf.m, p: payload.kdf.p, dkLen: payload.kdf.dkLen } : argon2Opts ?? {};
918
- const key = await deriveKeyFromPasswordArgon2(password, salt, effectiveOpts);
1016
+ const key = precomputed?.key ?? await deriveKeyFromPasswordArgon2(password, precomputed?.salt ?? salt, effectiveOpts);
919
1017
  const plaintext = decryptVault(
920
1018
  {
921
1019
  ciphertext: payload.ciphertext,
@@ -1627,7 +1725,6 @@ function isEnpassCsv(text) {
1627
1725
  const firstLine = text.split(/\r?\n/)[0];
1628
1726
  if (!firstLine) return false;
1629
1727
  const rawHeaders = firstLine.split(",");
1630
- const headers = rawHeaders.map((h) => h.replace(/^["']|["']$/g, "").trim());
1631
1728
  return rawHeaders.some((h) => {
1632
1729
  const clean = h.replace(/^["']|["']$/g, "").trim();
1633
1730
  return clean === "Title";
@@ -2165,22 +2262,28 @@ function parseCsvNoteExtras(rawNote) {
2165
2262
  let totpSecret;
2166
2263
  const customFields = [];
2167
2264
  for (const line of lines) {
2168
- const to = line.match(/^TOTP:\s+(.+)$/);
2169
- if (to) {
2170
- totpSecret = to[1].trim();
2171
- continue;
2265
+ if (line.startsWith("TOTP:")) {
2266
+ let j = 5;
2267
+ while (j < line.length && /\s/.test(line.charAt(j))) j++;
2268
+ if (j > 5 && j < line.length) {
2269
+ totpSecret = line.slice(j).trim();
2270
+ continue;
2271
+ }
2172
2272
  }
2173
- const fieldsMatch = line.match(/^Fields:\s+(.+)$/);
2174
- if (fieldsMatch) {
2175
- const entries = fieldsMatch[1].split("|");
2176
- for (const entry of entries) {
2177
- const colonIdx = entry.indexOf(":");
2178
- if (colonIdx > 0) {
2179
- customFields.push({
2180
- name: entry.slice(0, colonIdx).trim(),
2181
- value: entry.slice(colonIdx + 1).trim(),
2182
- type: "text"
2183
- });
2273
+ if (line.startsWith("Fields:")) {
2274
+ let j = 7;
2275
+ while (j < line.length && /\s/.test(line.charAt(j))) j++;
2276
+ if (j > 7 && j < line.length) {
2277
+ const entries = line.slice(j).split("|");
2278
+ for (const entry of entries) {
2279
+ const colonIdx = entry.indexOf(":");
2280
+ if (colonIdx > 0) {
2281
+ customFields.push({
2282
+ name: entry.slice(0, colonIdx).trim(),
2283
+ value: entry.slice(colonIdx + 1).trim(),
2284
+ type: "text"
2285
+ });
2286
+ }
2184
2287
  }
2185
2288
  }
2186
2289
  }
@@ -2681,7 +2784,6 @@ function exportCsv(vault, opts = {}) {
2681
2784
  continue;
2682
2785
  }
2683
2786
  if (e.folder === "cartes") {
2684
- const m = parseCardMeta(e.notes);
2685
2787
  rows.push(
2686
2788
  [
2687
2789
  csvEscape("cartes"),
@@ -3037,7 +3139,10 @@ var import_sha25 = require("@noble/hashes/sha2.js");
3037
3139
  var TOTP_STEP_SEC = 30;
3038
3140
  function base32Decode(input) {
3039
3141
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
3040
- const clean = input.replace(/\s/g, "").toUpperCase().replace(/=+$/, "");
3142
+ const upper = input.replace(/\s/g, "").toUpperCase();
3143
+ let end = upper.length;
3144
+ while (end > 0 && upper.charAt(end - 1) === "=") end--;
3145
+ const clean = upper.slice(0, end);
3041
3146
  const bits = [];
3042
3147
  for (let i = 0; i < clean.length; i++) {
3043
3148
  const idx = alphabet.indexOf(clean[i]);
@@ -3108,9 +3213,9 @@ function parseTOTPUri(uri) {
3108
3213
  var import_legacy2 = require("@noble/hashes/legacy.js");
3109
3214
  var import_utils9 = require("@noble/hashes/utils.js");
3110
3215
  var RANGE_BASE = "https://api.pwnedpasswords.com/range/";
3111
- async function getPwnedPasswordCount(password, options) {
3112
- if (!password.length) return 0;
3113
- const hash = (0, import_utils9.bytesToHex)((0, import_legacy2.sha1)((0, import_utils9.utf8ToBytes)(password))).toUpperCase();
3216
+ async function getPwnedCountForHash(sha1Hex, options) {
3217
+ const hash = sha1Hex.trim().toUpperCase();
3218
+ if (hash.length !== 40) throw new Error(`Invalid SHA-1 length: ${hash.length}`);
3114
3219
  const prefix = hash.slice(0, 5);
3115
3220
  const suffix = hash.slice(5);
3116
3221
  const fetchFn = options?.fetchFn ?? fetch;
@@ -3126,7 +3231,6 @@ async function getPwnedPasswordCount(password, options) {
3126
3231
  throw new Error(`PwnedPasswords API: ${res.status}`);
3127
3232
  }
3128
3233
  const text = await res.text();
3129
- const suffixUpper = suffix.toUpperCase();
3130
3234
  for (const line of text.split("\n")) {
3131
3235
  const trimmed = line.trim();
3132
3236
  if (!trimmed) continue;
@@ -3134,13 +3238,18 @@ async function getPwnedPasswordCount(password, options) {
3134
3238
  if (colon < 0) continue;
3135
3239
  const h = trimmed.slice(0, colon).toUpperCase();
3136
3240
  const countStr = trimmed.slice(colon + 1);
3137
- if (h === suffixUpper) {
3241
+ if (h === suffix) {
3138
3242
  const n = parseInt(countStr, 10);
3139
3243
  return Number.isFinite(n) ? n : 0;
3140
3244
  }
3141
3245
  }
3142
3246
  return 0;
3143
3247
  }
3248
+ async function getPwnedPasswordCount(password, options) {
3249
+ if (!password.length) return 0;
3250
+ const hash = (0, import_utils9.bytesToHex)((0, import_legacy2.sha1)((0, import_utils9.utf8ToBytes)(password))).toUpperCase();
3251
+ return getPwnedCountForHash(hash, options);
3252
+ }
3144
3253
 
3145
3254
  // src/wordlist.ts
3146
3255
  var EFF_WORDLIST = [
@@ -11718,7 +11827,10 @@ function toBase64Url2(bytes) {
11718
11827
  for (let i = 0; i < bytes.length; i++) {
11719
11828
  binary += String.fromCharCode(bytes[i]);
11720
11829
  }
11721
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
11830
+ const b64 = btoa(binary).replace(/\+/g, "-").replace(/\//g, "_");
11831
+ let end = b64.length;
11832
+ while (end > 0 && b64.charAt(end - 1) === "=") end -= 1;
11833
+ return b64.slice(0, end);
11722
11834
  }
11723
11835
  function fromBase64Url2(str) {
11724
11836
  const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
@@ -11781,6 +11893,26 @@ async function getHiddenWalletFromPassword(masterPassword, secretKey) {
11781
11893
  _hwCache.set(cacheKey, account);
11782
11894
  return account;
11783
11895
  }
11896
+ async function getHiddenWalletFromSecretKey(secretKey) {
11897
+ const cacheKey = (0, import_viem.keccak256)((0, import_viem.toHex)(`${secretKey}:sk-only:v1`));
11898
+ const cached = _hwCache.get(cacheKey);
11899
+ if (cached) return cached;
11900
+ const privateKey = await getHiddenWalletPrivateKeyFromSecretKey(secretKey);
11901
+ const account = (0, import_accounts.privateKeyToAccount)(privateKey);
11902
+ _hwCache.set(cacheKey, account);
11903
+ return account;
11904
+ }
11905
+ async function getHiddenWalletPrivateKeyFromSecretKey(secretKey) {
11906
+ const salt = deriveHwSalt(secretKey);
11907
+ const inputBytes = new TextEncoder().encode(secretKey);
11908
+ let derived;
11909
+ if (_argon2Provider) {
11910
+ derived = await _argon2Provider(inputBytes, salt, HW_ARGON2_OPTS);
11911
+ } else {
11912
+ derived = (0, import_argon22.argon2id)(inputBytes, salt, HW_ARGON2_OPTS);
11913
+ }
11914
+ return (0, import_viem.bytesToHex)(derived);
11915
+ }
11784
11916
  function clearHiddenWalletCache() {
11785
11917
  _hwCache.clear();
11786
11918
  }
@@ -11943,7 +12075,9 @@ function calculateMrzChecksum(str) {
11943
12075
  return sum % 10;
11944
12076
  }
11945
12077
  function cleanMrzField(str) {
11946
- return str.replace(/<+$/g, "").replace(/</g, " ").trim();
12078
+ let end = str.length;
12079
+ while (end > 0 && str.charAt(end - 1) === "<") end--;
12080
+ return str.slice(0, end).replace(/</g, " ").trim();
11947
12081
  }
11948
12082
  function parseMrzDate(str) {
11949
12083
  if (str.length !== 6) return "";
@@ -12212,6 +12346,7 @@ var import_secp256k12 = require("@noble/curves/secp256k1.js");
12212
12346
  var import_chacha7 = require("@noble/ciphers/chacha.js");
12213
12347
  var import_utils19 = require("@noble/ciphers/utils.js");
12214
12348
  var import_hkdf5 = require("@noble/hashes/hkdf.js");
12349
+ var import_hmac4 = require("@noble/hashes/hmac.js");
12215
12350
  var import_sha29 = require("@noble/hashes/sha2.js");
12216
12351
  var import_utils20 = require("@noble/hashes/utils.js");
12217
12352
  var NONCE_LENGTH5 = 24;
@@ -12226,8 +12361,10 @@ function generateSharePin() {
12226
12361
  const num = (bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]) >>> 0;
12227
12362
  return String(num % 1e6).padStart(PIN_LENGTH, "0");
12228
12363
  }
12229
- function computeApiPinHash(pin) {
12230
- return (0, import_utils20.bytesToHex)((0, import_sha29.sha256)((0, import_utils20.utf8ToBytes)(pin)));
12364
+ var PIN_PROOF_INFO = (0, import_utils20.utf8ToBytes)("vaultkeepr-share-pin-proof-v1");
12365
+ function computeApiPinHash(pin, keyHex) {
12366
+ const proofKey = (0, import_hmac4.hmac)(import_sha29.sha256, (0, import_utils20.hexToBytes)(keyHex), PIN_PROOF_INFO);
12367
+ return (0, import_utils20.bytesToHex)((0, import_hmac4.hmac)(import_sha29.sha256, proofKey, (0, import_utils20.utf8ToBytes)(pin)));
12231
12368
  }
12232
12369
  function generateShareId() {
12233
12370
  const ts = Date.now().toString(36);
@@ -12245,7 +12382,6 @@ function deriveShareKeyV1(sharedSecret, pin) {
12245
12382
  function createSecureShare(payload, pin) {
12246
12383
  const sharePin = pin ?? generateSharePin();
12247
12384
  const shareId = generateShareId();
12248
- const apiPinHash = computeApiPinHash(sharePin);
12249
12385
  const fullPayload = {
12250
12386
  ...payload,
12251
12387
  version: SHARE_VERSION,
@@ -12258,6 +12394,7 @@ function createSecureShare(payload, pin) {
12258
12394
  }
12259
12395
  const ephemeralPrivate = import_secp256k12.secp256k1.utils.randomSecretKey();
12260
12396
  const ephemeralPublic = import_secp256k12.secp256k1.getPublicKey(ephemeralPrivate, false);
12397
+ const apiPinHash = computeApiPinHash(sharePin, (0, import_utils20.bytesToHex)(ephemeralPrivate));
12261
12398
  const shareKey = deriveShareKeyV2(ephemeralPrivate, sharePin);
12262
12399
  const nonce = (0, import_utils19.randomBytes)(NONCE_LENGTH5);
12263
12400
  const chacha = (0, import_chacha7.xchacha20poly1305)(shareKey, nonce);
@@ -12393,7 +12530,7 @@ function calculatePasswordStrength(password) {
12393
12530
  } else {
12394
12531
  feedback.push("passwordFeedback.addSpecialChars");
12395
12532
  }
12396
- if (!/(.)\\1{2,}/.test(password)) {
12533
+ if (!/(.)\1{2,}/.test(password)) {
12397
12534
  score += 10;
12398
12535
  } else {
12399
12536
  feedback.push("passwordFeedback.avoidRepeated");
@@ -12600,6 +12737,59 @@ function mergePrfCredentials(local, remote) {
12600
12737
  for (const cred of local) byId.set(cred.credentialId, cred);
12601
12738
  return Array.from(byId.values());
12602
12739
  }
12740
+ function legacyContactKey(c) {
12741
+ return JSON.stringify([c.label, c.email, c.telegram, c.address, c.publicKey]);
12742
+ }
12743
+ function mergeLegacyContacts(base, local, remote) {
12744
+ const byKey = /* @__PURE__ */ new Map();
12745
+ for (const c of [...local ?? [], ...remote ?? []]) {
12746
+ const key = legacyContactKey(c);
12747
+ const existing = byKey.get(key);
12748
+ if (!existing) {
12749
+ byKey.set(key, c);
12750
+ continue;
12751
+ }
12752
+ if (c.addedAt !== existing.addedAt) {
12753
+ byKey.set(key, c.addedAt > existing.addedAt ? c : existing);
12754
+ } else {
12755
+ byKey.set(key, deterministicPick(existing, c));
12756
+ }
12757
+ }
12758
+ if (byKey.size === 0) return void 0;
12759
+ const ordered = [];
12760
+ const placed = /* @__PURE__ */ new Set();
12761
+ for (const c of base ?? []) {
12762
+ const key = legacyContactKey(c);
12763
+ const kept = byKey.get(key);
12764
+ if (kept && !placed.has(key)) {
12765
+ placed.add(key);
12766
+ ordered.push(kept);
12767
+ }
12768
+ }
12769
+ const additions = Array.from(byKey.entries()).filter(([key]) => !placed.has(key));
12770
+ additions.sort((a, b) => {
12771
+ if (a[1].addedAt !== b[1].addedAt) return a[1].addedAt < b[1].addedAt ? -1 : 1;
12772
+ return JSON.stringify(a[1]) <= JSON.stringify(b[1]) ? -1 : 1;
12773
+ });
12774
+ for (const [, c] of additions) ordered.push(c);
12775
+ return ordered;
12776
+ }
12777
+ function mergeMonitoredEmails(base, local, remote) {
12778
+ const present = /* @__PURE__ */ new Set();
12779
+ for (const v of [...local ?? [], ...remote ?? []]) present.add(v);
12780
+ if (present.size === 0) return void 0;
12781
+ const ordered = [];
12782
+ for (const v of base ?? []) {
12783
+ if (present.has(v) && !ordered.includes(v)) ordered.push(v);
12784
+ }
12785
+ const additions = [];
12786
+ for (const v of present) {
12787
+ if (!ordered.includes(v)) additions.push(v);
12788
+ }
12789
+ additions.sort((a, b) => a.localeCompare(b));
12790
+ for (const v of additions) ordered.push(v);
12791
+ return ordered;
12792
+ }
12603
12793
  function threeWayMerge(base, local, remote) {
12604
12794
  const effectiveBase = base ?? {
12605
12795
  version: local.version,
@@ -12663,6 +12853,16 @@ function threeWayMerge(base, local, remote) {
12663
12853
  (sum, f) => sum + (f.originalSize || 0),
12664
12854
  0
12665
12855
  );
12856
+ const mergedLegacyContacts = mergeLegacyContacts(
12857
+ base ? base.legacyContacts : void 0,
12858
+ local.legacyContacts,
12859
+ remote.legacyContacts
12860
+ );
12861
+ const mergedMonitoredEmails = mergeMonitoredEmails(
12862
+ base ? base.monitoredEmails : void 0,
12863
+ local.monitoredEmails,
12864
+ remote.monitoredEmails
12865
+ );
12666
12866
  const out = {
12667
12867
  version: Math.max(local.version, remote.version),
12668
12868
  createdAt: local.createdAt,
@@ -12687,6 +12887,15 @@ function threeWayMerge(base, local, remote) {
12687
12887
  if (Object.keys(mergedEntryTombstones).length > 0) {
12688
12888
  out.entryTombstones = mergedEntryTombstones;
12689
12889
  }
12890
+ if (mergedLegacyContacts) {
12891
+ out.legacyContacts = mergedLegacyContacts;
12892
+ }
12893
+ if (mergedMonitoredEmails) {
12894
+ out.monitoredEmails = mergedMonitoredEmails;
12895
+ }
12896
+ if (local.secretKey !== void 0 || remote.secretKey !== void 0) {
12897
+ out.secretKey = local.secretKey !== void 0 && remote.secretKey !== void 0 ? deterministicPick(local.secretKey, remote.secretKey) : local.secretKey ?? remote.secretKey;
12898
+ }
12690
12899
  return out;
12691
12900
  }
12692
12901
  function mergeFolders(base, local, remote, baseTombstones, localTombstones, remoteTombstones, now) {
@@ -14928,7 +15137,7 @@ var BIP39_WORDLIST = [
14928
15137
  "zoo"
14929
15138
  ];
14930
15139
  function suggestBip39Words(prefix, limit = 5) {
14931
- const p = prefix.toLowerCase().trim();
15140
+ const p = (prefix ?? "").toLowerCase().trim();
14932
15141
  if (!p) return [];
14933
15142
  const results = [];
14934
15143
  for (const word of BIP39_WORDLIST) {
@@ -15206,6 +15415,146 @@ var VaultKeepR_SLM = class {
15206
15415
  return { severity, ...data };
15207
15416
  }
15208
15417
  };
15418
+
15419
+ // src/account-state.ts
15420
+ function accountKeyState(input) {
15421
+ if (input.hasSecretKey) return { kind: "ready" };
15422
+ if (input.remote?.moved) return { kind: "read_only_pair" };
15423
+ if (input.remote?.exists || input.hasLocalVault) return { kind: "passage" };
15424
+ return { kind: "choose" };
15425
+ }
15426
+ function pointerWalletFor(input) {
15427
+ return input.hasSecretKey && input.remoteMoved ? input.smartAccount : input.eoa;
15428
+ }
15429
+ function passageNext(step, event) {
15430
+ switch (`${step}:${event}`) {
15431
+ case "idle:key_generated":
15432
+ return { step: "key_pending", discardPendingKey: false };
15433
+ case "key_pending:copy_ok":
15434
+ return { step: "copied", discardPendingKey: false };
15435
+ case "key_pending:copy_failed":
15436
+ return { step: "idle", discardPendingKey: true };
15437
+ case "copied:move_ok":
15438
+ return { step: "moved", discardPendingKey: false };
15439
+ case "copied:move_failed":
15440
+ return { step: "copied", discardPendingKey: false };
15441
+ case "copied:move_conflict":
15442
+ return { step: "idle", discardPendingKey: true };
15443
+ case "moved:promote_ok":
15444
+ return { step: "done", discardPendingKey: false };
15445
+ default:
15446
+ return { error: `invalid passage transition: ${step} + ${event}` };
15447
+ }
15448
+ }
15449
+ function passageIdentity(step) {
15450
+ return step === "done" ? "smart_account" : "legacy";
15451
+ }
15452
+
15453
+ // src/recovery-code.ts
15454
+ var import_argon23 = require("@noble/hashes/argon2.js");
15455
+ var import_utils23 = require("@noble/hashes/utils.js");
15456
+ var import_utils24 = require("@noble/ciphers/utils.js");
15457
+ var RECOVERY_CODE_LENGTH = 20;
15458
+ var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
15459
+ var ID_SALT = new TextEncoder().encode("vaultkeepr-recovery-id-v1");
15460
+ var SALT_LENGTH2 = 16;
15461
+ var ARGON2_OPTS2 = { t: 3, m: 65536, p: 4, dkLen: 32 };
15462
+ var SECRET_KEY_HEX = /^[0-9a-fA-F]{64}$/;
15463
+ function isAccountKeyText(value) {
15464
+ if (SECRET_KEY_HEX.test(value)) return true;
15465
+ if (value.length < 32 || value.length > 4096 || value.includes("BEGIN PGP")) return false;
15466
+ return !isEnvelopeText(value);
15467
+ }
15468
+ function isEnvelopeText(value) {
15469
+ if (!value.startsWith("{")) return false;
15470
+ try {
15471
+ const o = JSON.parse(value);
15472
+ return !!o && typeof o === "object" && typeof o.v === "number" && typeof o.iv === "string" && typeof o.ct === "string";
15473
+ } catch {
15474
+ return false;
15475
+ }
15476
+ }
15477
+ var argon2RecoveryKdf = (codeBytes2, salt) => (0, import_argon23.argon2id)(codeBytes2, salt, ARGON2_OPTS2);
15478
+ function generateRecoveryCode() {
15479
+ const bytes = (0, import_utils24.randomBytes)(RECOVERY_CODE_LENGTH);
15480
+ let code = "";
15481
+ for (const b of bytes) code += ALPHABET[b % 32];
15482
+ return code;
15483
+ }
15484
+ function normalizeRecoveryCode(input) {
15485
+ return input.toUpperCase().replace(/[\s-]/g, "").replace(/O/g, "0").replace(/[IL]/g, "1");
15486
+ }
15487
+ function isValidRecoveryCode(input) {
15488
+ const code = normalizeRecoveryCode(input);
15489
+ return code.length === RECOVERY_CODE_LENGTH && [...code].every((c) => ALPHABET.includes(c));
15490
+ }
15491
+ function formatRecoveryCode(code) {
15492
+ const canonical = normalizeRecoveryCode(code);
15493
+ return canonical.match(/.{1,5}/g)?.join("-") ?? canonical;
15494
+ }
15495
+ function codeBytes(code) {
15496
+ const canonical = normalizeRecoveryCode(code);
15497
+ if (!isValidRecoveryCode(canonical)) throw new Error("Invalid recovery code");
15498
+ return new TextEncoder().encode(canonical);
15499
+ }
15500
+ async function deriveRecoveryId(code, kdf = argon2RecoveryKdf) {
15501
+ return (0, import_utils23.bytesToHex)(await kdf(codeBytes(code), ID_SALT));
15502
+ }
15503
+ async function createRecoveryBlob(secretKeyHex, code, kdf = argon2RecoveryKdf) {
15504
+ if (!isAccountKeyText(secretKeyHex)) throw new Error("Invalid secret key");
15505
+ const salt = (0, import_utils24.randomBytes)(SALT_LENGTH2);
15506
+ const key = await kdf(codeBytes(code), salt);
15507
+ const encrypted = encryptVault(secretKeyHex, key);
15508
+ return { version: 1, salt: (0, import_utils23.bytesToHex)(salt), encrypted };
15509
+ }
15510
+ async function openRecoveryBlob(blob, code, kdf = argon2RecoveryKdf) {
15511
+ if (blob.version !== 1) throw new Error("Unsupported recovery blob version");
15512
+ let plain;
15513
+ try {
15514
+ const key = await kdf(codeBytes(code), (0, import_utils23.hexToBytes)(blob.salt));
15515
+ plain = decryptVault(blob.encrypted, key);
15516
+ } catch {
15517
+ throw new Error("Recovery failed");
15518
+ }
15519
+ const secretKey = typeof plain === "string" ? plain : new TextDecoder().decode(plain);
15520
+ if (!isAccountKeyText(secretKey)) throw new Error("Recovery failed");
15521
+ return secretKey;
15522
+ }
15523
+
15524
+ // src/key-join.ts
15525
+ var KeyJoinError = class extends Error {
15526
+ constructor(code) {
15527
+ super(code);
15528
+ this.code = code;
15529
+ this.name = "KeyJoinError";
15530
+ }
15531
+ code;
15532
+ };
15533
+ async function prepareKeyJoin(secretKeyHex, post, kdf) {
15534
+ const code = generateRecoveryCode();
15535
+ const blob = await createRecoveryBlob(secretKeyHex, code, kdf);
15536
+ const id = await deriveRecoveryId(code, kdf);
15537
+ await post(id, blob);
15538
+ return { code: formatRecoveryCode(code) };
15539
+ }
15540
+ async function joinWithKeyCode(input, fetchBlob, kdf) {
15541
+ const code = normalizeRecoveryCode(input);
15542
+ if (!isValidRecoveryCode(code)) throw new KeyJoinError("invalid_code");
15543
+ const id = await deriveRecoveryId(code, kdf);
15544
+ const blob = await fetchBlob(id);
15545
+ if (!blob) throw new KeyJoinError("not_found");
15546
+ return openRecoveryBlob(blob, code, kdf);
15547
+ }
15548
+
15549
+ // src/import-key-policy.ts
15550
+ function decideEncryptedImport(input) {
15551
+ const device = input.deviceKey ? input.deviceKey.toLowerCase() : null;
15552
+ const file = input.fileKey ? input.fileKey.toLowerCase() : null;
15553
+ if (!device && !file) return { action: "import_keyless" };
15554
+ if (!device && file) return { action: "join_with_file_key" };
15555
+ if (device && !file) return { action: "refuse", reason: "keyless_file_on_keyed_device" };
15556
+ return device === file ? { action: "keep_device_key" } : { action: "refuse", reason: "other_account" };
15557
+ }
15209
15558
  // Annotate the CommonJS export names for ESM import in node:
15210
15559
  0 && (module.exports = {
15211
15560
  BIP39_WORDLIST,
@@ -15221,6 +15570,7 @@ var VaultKeepR_SLM = class {
15221
15570
  EXTENSION_MIN_PAYLOAD_VERSION,
15222
15571
  FRAGMENTED_DEFAULT_THRESHOLD,
15223
15572
  FRAGMENTED_DEFAULT_TOTAL,
15573
+ KeyJoinError,
15224
15574
  MOBILE_MAX_PAYLOAD_VERSION,
15225
15575
  MOBILE_MIN_PAYLOAD_VERSION,
15226
15576
  P256_SPKI_HEADER,
@@ -15231,11 +15581,14 @@ var VaultKeepR_SLM = class {
15231
15581
  PAYLOAD_VERSION_V2,
15232
15582
  PAYLOAD_VERSION_V3,
15233
15583
  PAYLOAD_VERSION_V4,
15584
+ RECOVERY_CODE_LENGTH,
15234
15585
  TAG_COLORS,
15235
15586
  VALID_TAGS,
15236
15587
  VaultKeepR_SLM,
15588
+ accountKeyState,
15237
15589
  addEntry,
15238
15590
  analyzeVaultHealth,
15591
+ argon2RecoveryKdf,
15239
15592
  buildAttestationObject,
15240
15593
  buildAuthDataForCreate,
15241
15594
  buildAuthDataForGet,
@@ -15250,6 +15603,7 @@ var VaultKeepR_SLM = class {
15250
15603
  captureError,
15251
15604
  checkEmailBreaches,
15252
15605
  checkVaultBreaches,
15606
+ cleanEntryTitle,
15253
15607
  cleanMrzField,
15254
15608
  clearErrorBuffer,
15255
15609
  clearHiddenWalletCache,
@@ -15260,8 +15614,10 @@ var VaultKeepR_SLM = class {
15260
15614
  createExportPayload,
15261
15615
  createFolder,
15262
15616
  createKeyEnvelope,
15617
+ createRecoveryBlob,
15263
15618
  createSecureShare,
15264
15619
  createVaultPayload,
15620
+ decideEncryptedImport,
15265
15621
  decompressVault,
15266
15622
  decryptFromPair,
15267
15623
  decryptKeyEnvelope,
@@ -15283,6 +15639,7 @@ var VaultKeepR_SLM = class {
15283
15639
  deriveMrzKseed,
15284
15640
  deriveNfcKey,
15285
15641
  derivePairSecret,
15642
+ deriveRecoveryId,
15286
15643
  digitsOnly,
15287
15644
  encryptAndFragmentDocument,
15288
15645
  encryptForPair,
@@ -15300,6 +15657,7 @@ var VaultKeepR_SLM = class {
15300
15657
  formatCardDisplayMasked,
15301
15658
  formatCardNumber,
15302
15659
  formatErrorReport,
15660
+ formatRecoveryCode,
15303
15661
  fromBase64Url,
15304
15662
  generateBlurredThumbnailCanvas,
15305
15663
  generateCredentialId,
@@ -15311,17 +15669,22 @@ var VaultKeepR_SLM = class {
15311
15669
  generatePassphrase,
15312
15670
  generatePassword,
15313
15671
  generatePrfSalt,
15672
+ generateRecoveryCode,
15314
15673
  generateSaltArgon2,
15315
15674
  generateSharePin,
15316
15675
  getCardBrand,
15317
15676
  getChecksumAddress,
15677
+ getEntryDisplayName,
15318
15678
  getEntryGroupLabel,
15319
15679
  getErrorBuffer,
15320
15680
  getHiddenWalletFromPassword,
15681
+ getHiddenWalletFromSecretKey,
15321
15682
  getHiddenWalletLegacy,
15322
15683
  getHiddenWalletPrivateKey,
15684
+ getHiddenWalletPrivateKeyFromSecretKey,
15323
15685
  getLast4,
15324
15686
  getMostUsedEntries,
15687
+ getPwnedCountForHash,
15325
15688
  getPwnedPasswordCount,
15326
15689
  getTOTPCode,
15327
15690
  getTOTPRemainingSeconds,
@@ -15335,6 +15698,7 @@ var VaultKeepR_SLM = class {
15335
15698
  importProtonPassJson,
15336
15699
  importVaultText,
15337
15700
  isAcceptablePayloadVersion,
15701
+ isAccountKeyText,
15338
15702
  isCurrentPayloadVersion,
15339
15703
  isEncryptedExport,
15340
15704
  isNfcPayloadV1,
@@ -15346,15 +15710,19 @@ var VaultKeepR_SLM = class {
15346
15710
  isPasskeyPrivateKeyEncrypted,
15347
15711
  isPrfSupported,
15348
15712
  isProtonPassExport,
15713
+ isValidRecoveryCode,
15349
15714
  isWebAuthnSupported,
15715
+ joinWithKeyCode,
15350
15716
  maskCardNumber,
15351
15717
  mergeFolderTrees,
15352
15718
  mergeFolders,
15353
15719
  mergeFragments,
15354
15720
  mergeImportedEntries,
15355
15721
  moveFolder,
15722
+ normalizeRecoveryCode,
15356
15723
  normalizeSignatureForKdf,
15357
15724
  normalizeUrl,
15725
+ openRecoveryBlob,
15358
15726
  parseMRZ,
15359
15727
  parseMrzDate,
15360
15728
  parsePairPayload,
@@ -15364,6 +15732,10 @@ var VaultKeepR_SLM = class {
15364
15732
  parseTOTPUri,
15365
15733
  parseVault,
15366
15734
  parseVaultPayload,
15735
+ passageIdentity,
15736
+ passageNext,
15737
+ pointerWalletFor,
15738
+ prepareKeyJoin,
15367
15739
  publicKeyToCose,
15368
15740
  publicKeyToSpki,
15369
15741
  reassembleAndDecryptDocument,