@bsv/wallet-toolbox-mobile 2.1.9 → 2.1.11

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.
Files changed (56) hide show
  1. package/out/src/CWIStyleWalletManager.d.ts +57 -5
  2. package/out/src/CWIStyleWalletManager.d.ts.map +1 -1
  3. package/out/src/CWIStyleWalletManager.js +282 -46
  4. package/out/src/CWIStyleWalletManager.js.map +1 -1
  5. package/out/src/SetupClient.d.ts.map +1 -1
  6. package/out/src/SetupClient.js +1 -2
  7. package/out/src/SetupClient.js.map +1 -1
  8. package/out/src/WalletAuthenticationManager.d.ts +1 -1
  9. package/out/src/WalletAuthenticationManager.d.ts.map +1 -1
  10. package/out/src/WalletAuthenticationManager.js +41 -31
  11. package/out/src/WalletAuthenticationManager.js.map +1 -1
  12. package/out/src/monitor/Monitor.d.ts +4 -3
  13. package/out/src/monitor/Monitor.d.ts.map +1 -1
  14. package/out/src/monitor/Monitor.js +39 -11
  15. package/out/src/monitor/Monitor.js.map +1 -1
  16. package/out/src/monitor/tasks/TaskReviewDoubleSpends.d.ts +26 -0
  17. package/out/src/monitor/tasks/TaskReviewDoubleSpends.d.ts.map +1 -0
  18. package/out/src/monitor/tasks/TaskReviewDoubleSpends.js +124 -0
  19. package/out/src/monitor/tasks/TaskReviewDoubleSpends.js.map +1 -0
  20. package/out/src/monitor/tasks/TaskReviewProvenTxs.d.ts +34 -0
  21. package/out/src/monitor/tasks/TaskReviewProvenTxs.d.ts.map +1 -0
  22. package/out/src/monitor/tasks/TaskReviewProvenTxs.js +131 -0
  23. package/out/src/monitor/tasks/TaskReviewProvenTxs.js.map +1 -0
  24. package/out/src/monitor/tasks/TaskReviewUtxos.d.ts +23 -0
  25. package/out/src/monitor/tasks/TaskReviewUtxos.d.ts.map +1 -0
  26. package/out/src/monitor/tasks/TaskReviewUtxos.js +71 -0
  27. package/out/src/monitor/tasks/TaskReviewUtxos.js.map +1 -0
  28. package/out/src/monitor/tasks/TaskSendWaiting.d.ts +14 -1
  29. package/out/src/monitor/tasks/TaskSendWaiting.d.ts.map +1 -1
  30. package/out/src/monitor/tasks/TaskSendWaiting.js +86 -20
  31. package/out/src/monitor/tasks/TaskSendWaiting.js.map +1 -1
  32. package/out/src/sdk/WalletStorage.interfaces.d.ts +9 -0
  33. package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  34. package/out/src/services/Services.d.ts.map +1 -1
  35. package/out/src/services/Services.js +10 -2
  36. package/out/src/services/Services.js.map +1 -1
  37. package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.js +1 -1
  38. package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.js.map +1 -1
  39. package/out/src/services/createDefaultWalletServicesOptions.js +1 -1
  40. package/out/src/services/createDefaultWalletServicesOptions.js.map +1 -1
  41. package/out/src/storage/StorageProvider.d.ts +6 -1
  42. package/out/src/storage/StorageProvider.d.ts.map +1 -1
  43. package/out/src/storage/StorageProvider.js +6 -0
  44. package/out/src/storage/StorageProvider.js.map +1 -1
  45. package/out/src/storage/StorageReaderWriter.d.ts +2 -1
  46. package/out/src/storage/StorageReaderWriter.d.ts.map +1 -1
  47. package/out/src/storage/StorageReaderWriter.js.map +1 -1
  48. package/out/src/storage/WalletStorageManager.d.ts +7 -0
  49. package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  50. package/out/src/storage/WalletStorageManager.js +33 -2
  51. package/out/src/storage/WalletStorageManager.js.map +1 -1
  52. package/package.json +4 -2
  53. package/out/src/monitor/tasks/TaskSyncWhenIdle.d.ts +0 -12
  54. package/out/src/monitor/tasks/TaskSyncWhenIdle.d.ts.map +0 -1
  55. package/out/src/monitor/tasks/TaskSyncWhenIdle.js +0 -22
  56. package/out/src/monitor/tasks/TaskSyncWhenIdle.js.map +0 -1
@@ -1,15 +1,64 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CWIStyleWalletManager = exports.OverlayUMPTokenInteractor = exports.DEFAULT_PROFILE_ID = exports.PBKDF2_NUM_ROUNDS = void 0;
3
+ exports.CWIStyleWalletManager = exports.OverlayUMPTokenInteractor = exports.DEFAULT_PROFILE_ID = exports.ARGON2ID_DEFAULT_HASH_LENGTH = exports.ARGON2ID_DEFAULT_PARALLELISM = exports.ARGON2ID_DEFAULT_MEMORY_KIB = exports.ARGON2ID_DEFAULT_ITERATIONS = exports.PBKDF2_NUM_ROUNDS = void 0;
4
4
  const sdk_1 = require("@bsv/sdk");
5
+ const hash_wasm_1 = require("hash-wasm");
5
6
  const PrivilegedKeyManager_1 = require("./sdk/PrivilegedKeyManager");
6
7
  /**
7
8
  * Number of rounds used in PBKDF2 for deriving password keys.
8
9
  */
9
10
  exports.PBKDF2_NUM_ROUNDS = 7777;
10
11
  /**
11
- * PBKDF-2 that prefers the browser / Node 20+ WebCrypto implementation and
12
- * silently falls back to the existing JS code.
12
+ * Default Argon2id parameters for password-key derivation (UMP v3).
13
+ */
14
+ exports.ARGON2ID_DEFAULT_ITERATIONS = 7;
15
+ exports.ARGON2ID_DEFAULT_MEMORY_KIB = 131072;
16
+ exports.ARGON2ID_DEFAULT_PARALLELISM = 1;
17
+ exports.ARGON2ID_DEFAULT_HASH_LENGTH = 32;
18
+ function isLikelyDerSignatureField(field) {
19
+ if (!field || field.length < 8 || field.length > 80) {
20
+ return false;
21
+ }
22
+ if (field[0] !== 0x30) {
23
+ return false;
24
+ }
25
+ const declaredLen = field[1];
26
+ return declaredLen === field.length - 2;
27
+ }
28
+ function stripVerifiedPushDropSignature(fields, lockingPublicKey) {
29
+ if (fields.length <= 11) {
30
+ return fields;
31
+ }
32
+ const protocolFieldCount = fields.length - 1;
33
+ const trailingField = fields[protocolFieldCount];
34
+ if (!trailingField || !isLikelyDerSignatureField(trailingField)) {
35
+ return fields;
36
+ }
37
+ try {
38
+ let dataLength = 0;
39
+ for (let i = 0; i < protocolFieldCount; i++) {
40
+ dataLength += fields[i].length;
41
+ }
42
+ const dataToVerify = new Array(dataLength);
43
+ let writeOffset = 0;
44
+ for (let i = 0; i < protocolFieldCount; i++) {
45
+ const field = fields[i];
46
+ for (let j = 0; j < field.length; j++) {
47
+ dataToVerify[writeOffset++] = field[j];
48
+ }
49
+ }
50
+ const signature = sdk_1.Signature.fromDER(trailingField);
51
+ if (signature.verify(dataToVerify, lockingPublicKey)) {
52
+ return fields.slice(0, protocolFieldCount);
53
+ }
54
+ }
55
+ catch (_a) {
56
+ return fields;
57
+ }
58
+ return fields;
59
+ }
60
+ /**
61
+ * PBKDF-2 that prefers WebCrypto and falls back to hash-wasm.
13
62
  *
14
63
  * @param passwordBytes Raw password bytes.
15
64
  * @param salt Salt bytes.
@@ -18,7 +67,7 @@ exports.PBKDF2_NUM_ROUNDS = 7777;
18
67
  * @param hash Digest algorithm (default "sha512").
19
68
  * @returns Derived key bytes.
20
69
  */
21
- async function pbkdf2NativeOrJs(passwordBytes, salt, iterations, keyLen, hash = 'sha512') {
70
+ async function pbkdf2NativeOrWasm(passwordBytes, salt, iterations, keyLen, hash = 'sha512') {
22
71
  var _a;
23
72
  // ----- fast-path: WebCrypto (both browser & recent Node expose globalThis.crypto.subtle)
24
73
  const subtle = (_a = globalThis === null || globalThis === void 0 ? void 0 : globalThis.crypto) === null || _a === void 0 ? void 0 : _a.subtle;
@@ -39,8 +88,52 @@ async function pbkdf2NativeOrJs(passwordBytes, salt, iterations, keyLen, hash =
39
88
  /* fall through */
40
89
  }
41
90
  }
42
- // ----- slow-path: old JavaScript implementation
43
- return sdk_1.Hash.pbkdf2(passwordBytes, salt, iterations, keyLen, hash);
91
+ const hashFunction = hash === 'sha256' ? (0, hash_wasm_1.createSHA256)() : (0, hash_wasm_1.createSHA512)();
92
+ const derived = await (0, hash_wasm_1.pbkdf2)({
93
+ password: new Uint8Array(passwordBytes),
94
+ salt: new Uint8Array(salt),
95
+ iterations,
96
+ hashLength: keyLen,
97
+ hashFunction,
98
+ outputType: 'binary'
99
+ });
100
+ return Array.from(derived);
101
+ }
102
+ /**
103
+ * Derives the password key from a password using the KDF specified in the UMP token.
104
+ * For legacy tokens (no KDF metadata), uses PBKDF2 with fixed rounds (7777).
105
+ * For v3 tokens, uses the algorithm specified in passwordKdf metadata.
106
+ *
107
+ * @param token The UMP token containing KDF metadata and salt.
108
+ * @param passwordBytes Raw password bytes.
109
+ * @param overrideKdf Optional KDF config to override token/default settings.
110
+ * @returns Derived password key bytes.
111
+ */
112
+ async function derivePasswordKey(token, passwordBytes, overrideKdf) {
113
+ var _a, _b, _c, _d, _e;
114
+ const kdf = overrideKdf || token.passwordKdf;
115
+ // Legacy token or explicit PBKDF2 request
116
+ if (!kdf || kdf.algorithm === 'pbkdf2-sha512') {
117
+ return pbkdf2NativeOrWasm(passwordBytes, token.passwordSalt, (_a = kdf === null || kdf === void 0 ? void 0 : kdf.iterations) !== null && _a !== void 0 ? _a : exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
118
+ }
119
+ // Argon2id path (UMP v3)
120
+ if (kdf.algorithm === 'argon2id') {
121
+ const iterations = (_b = kdf.iterations) !== null && _b !== void 0 ? _b : exports.ARGON2ID_DEFAULT_ITERATIONS;
122
+ const memorySize = (_c = kdf.memoryKiB) !== null && _c !== void 0 ? _c : exports.ARGON2ID_DEFAULT_MEMORY_KIB;
123
+ const parallelism = (_d = kdf.parallelism) !== null && _d !== void 0 ? _d : exports.ARGON2ID_DEFAULT_PARALLELISM;
124
+ const hashLength = (_e = kdf.hashLength) !== null && _e !== void 0 ? _e : exports.ARGON2ID_DEFAULT_HASH_LENGTH;
125
+ const hash = await (0, hash_wasm_1.argon2id)({
126
+ password: new Uint8Array(passwordBytes),
127
+ salt: new Uint8Array(token.passwordSalt),
128
+ iterations,
129
+ memorySize,
130
+ parallelism,
131
+ hashLength,
132
+ outputType: 'binary'
133
+ });
134
+ return Array.from(hash);
135
+ }
136
+ throw new Error(`Unsupported KDF algorithm: ${kdf.algorithm}`);
44
137
  }
45
138
  /**
46
139
  * Unique Identifier for the default profile (16 zero bytes).
@@ -127,11 +220,32 @@ class OverlayUMPTokenInteractor {
127
220
  fields[8] = token.presentationKeyEncrypted;
128
221
  fields[9] = token.passwordKeyEncrypted;
129
222
  fields[10] = token.recoveryKeyEncrypted;
130
- // Optional field (11) for encrypted profiles
223
+ // Optional field for encrypted profiles
131
224
  if (token.profilesEncrypted) {
132
225
  fields[11] = token.profilesEncrypted;
133
226
  }
227
+ // V3 fields: umpVersion, kdfAlgorithm, kdfParams
228
+ if (token.umpVersion === 3 && token.passwordKdf) {
229
+ const versionFieldIndex = token.profilesEncrypted ? 12 : 11;
230
+ fields[versionFieldIndex] = [token.umpVersion]; // Single byte
231
+ fields[versionFieldIndex + 1] = sdk_1.Utils.toArray(token.passwordKdf.algorithm, 'utf8');
232
+ // Construct kdfParams JSON
233
+ const kdfParams = {
234
+ iterations: token.passwordKdf.iterations
235
+ };
236
+ if (token.passwordKdf.memoryKiB !== undefined) {
237
+ kdfParams.memoryKiB = token.passwordKdf.memoryKiB;
238
+ }
239
+ if (token.passwordKdf.parallelism !== undefined) {
240
+ kdfParams.parallelism = token.passwordKdf.parallelism;
241
+ }
242
+ if (token.passwordKdf.hashLength !== undefined) {
243
+ kdfParams.hashLength = token.passwordKdf.hashLength;
244
+ }
245
+ fields[versionFieldIndex + 2] = sdk_1.Utils.toArray(JSON.stringify(kdfParams), 'utf8');
246
+ }
134
247
  // 2) Create a PushDrop script referencing these fields, locked with the admin key.
248
+ // The signature will be added as trailing metadata by PushDrop (not part of protocol fields).
135
249
  const script = await new sdk_1.PushDrop(wallet, adminOriginator).lock(fields, [2, 'admin user management token'], // protocolID
136
250
  '1', // keyID
137
251
  'self', // counterparty
@@ -257,7 +371,7 @@ class OverlayUMPTokenInteractor {
257
371
  * @returns The parsed UMPToken or `undefined` if none found/decodable.
258
372
  */
259
373
  parseLookupAnswer(answer) {
260
- var _a;
374
+ var _a, _b, _c;
261
375
  if (answer.type !== 'output-list') {
262
376
  return undefined;
263
377
  }
@@ -274,23 +388,60 @@ class OverlayUMPTokenInteractor {
274
388
  console.warn(`Unexpected number of fields in UMP token: ${(_a = decoded.fields) === null || _a === void 0 ? void 0 : _a.length}`);
275
389
  return undefined;
276
390
  }
391
+ // Parse protocol fields, excluding the trailing PushDrop signature only when
392
+ // it is cryptographically valid for the preceding field payload.
393
+ let protocolFields = stripVerifiedPushDropSignature(decoded.fields, decoded.lockingPublicKey);
394
+ // Detect v3 token metadata in either layout:
395
+ // - with profilesEncrypted present: [0..10]=core, [11]=profiles, [12]=version, [13]=algorithm, [14]=params
396
+ // - without profilesEncrypted: [0..10]=core, [11]=version, [12]=algorithm, [13]=params
397
+ const hasV3MetadataWithProfiles = protocolFields.length >= 15 && ((_b = protocolFields[12]) === null || _b === void 0 ? void 0 : _b.length) === 1 && protocolFields[12][0] === 3;
398
+ const hasV3MetadataWithoutProfiles = protocolFields.length >= 14 && ((_c = protocolFields[11]) === null || _c === void 0 ? void 0 : _c.length) === 1 && protocolFields[11][0] === 3;
399
+ const kdfVersionFieldIndex = hasV3MetadataWithProfiles ? 12 : hasV3MetadataWithoutProfiles ? 11 : -1;
277
400
  // Build the UMP token from these fields, preserving outpoint
278
401
  const t = {
279
- // Order matches buildAndSend and serialize/deserialize
280
- passwordSalt: decoded.fields[0],
281
- passwordPresentationPrimary: decoded.fields[1],
282
- passwordRecoveryPrimary: decoded.fields[2],
283
- presentationRecoveryPrimary: decoded.fields[3],
284
- passwordPrimaryPrivileged: decoded.fields[4],
285
- presentationRecoveryPrivileged: decoded.fields[5],
286
- presentationHash: decoded.fields[6],
287
- recoveryHash: decoded.fields[7],
288
- presentationKeyEncrypted: decoded.fields[8],
289
- passwordKeyEncrypted: decoded.fields[9],
290
- recoveryKeyEncrypted: decoded.fields[10],
291
- profilesEncrypted: decoded.fields[12] ? decoded.fields[11] : undefined, // If there's a signature in field 12, use field 11
402
+ // Core fields (unchanged for all versions)
403
+ passwordSalt: protocolFields[0],
404
+ passwordPresentationPrimary: protocolFields[1],
405
+ passwordRecoveryPrimary: protocolFields[2],
406
+ presentationRecoveryPrimary: protocolFields[3],
407
+ passwordPrimaryPrivileged: protocolFields[4],
408
+ presentationRecoveryPrivileged: protocolFields[5],
409
+ presentationHash: protocolFields[6],
410
+ recoveryHash: protocolFields[7],
411
+ presentationKeyEncrypted: protocolFields[8],
412
+ passwordKeyEncrypted: protocolFields[9],
413
+ recoveryKeyEncrypted: protocolFields[10],
292
414
  currentOutpoint: outpoint
293
415
  };
416
+ // Encrypted profiles (optional, all versions)
417
+ const hasProfilesField = hasV3MetadataWithProfiles || (!hasV3MetadataWithoutProfiles && !!protocolFields[11]);
418
+ if (hasProfilesField && protocolFields[11] && protocolFields[11].length > 0) {
419
+ t.profilesEncrypted = protocolFields[11];
420
+ }
421
+ // V3 fields: umpVersion, kdfAlgorithm, kdfParams (fields 12, 13, 14)
422
+ if (kdfVersionFieldIndex !== -1) {
423
+ t.umpVersion = protocolFields[kdfVersionFieldIndex][0]; // Single byte value 3
424
+ const kdfAlgorithmField = protocolFields[kdfVersionFieldIndex + 1];
425
+ const kdfParamsField = protocolFields[kdfVersionFieldIndex + 2];
426
+ if (!kdfAlgorithmField || !kdfParamsField) {
427
+ return t;
428
+ }
429
+ const kdfAlgorithm = sdk_1.Utils.toUTF8(kdfAlgorithmField);
430
+ const kdfParamsJson = sdk_1.Utils.toUTF8(kdfParamsField);
431
+ try {
432
+ const kdfParams = JSON.parse(kdfParamsJson);
433
+ t.passwordKdf = {
434
+ algorithm: kdfAlgorithm,
435
+ iterations: kdfParams.iterations,
436
+ memoryKiB: kdfParams.memoryKiB,
437
+ parallelism: kdfParams.parallelism,
438
+ hashLength: kdfParams.hashLength
439
+ };
440
+ }
441
+ catch (e) {
442
+ console.warn('Failed to parse v3 KDF params:', e);
443
+ }
444
+ }
294
445
  return t;
295
446
  }
296
447
  catch (e) {
@@ -336,8 +487,10 @@ class CWIStyleWalletManager {
336
487
  * @param passwordRetriever A function to request the user's password.
337
488
  * @param newWalletFunder Optional function to fund a new wallet.
338
489
  * @param stateSnapshot Optional previously saved state snapshot.
490
+ * @param kdfConfig Optional KDF configuration for new UMP tokens.
339
491
  */
340
- constructor(adminOriginator, walletBuilder, interactor = new OverlayUMPTokenInteractor(), recoveryKeySaver, passwordRetriever, newWalletFunder, stateSnapshot) {
492
+ constructor(adminOriginator, walletBuilder, interactor = new OverlayUMPTokenInteractor(), recoveryKeySaver, passwordRetriever, newWalletFunder, stateSnapshot, kdfConfig) {
493
+ var _a, _b, _c, _d, _e;
341
494
  /**
342
495
  * Current mode of authentication.
343
496
  */
@@ -361,6 +514,14 @@ class CWIStyleWalletManager {
361
514
  this.passwordRetriever = passwordRetriever;
362
515
  this.authenticated = false;
363
516
  this.newWalletFunder = newWalletFunder;
517
+ // Initialize KDF config with Argon2id defaults for v3 tokens
518
+ this.kdfConfig = {
519
+ algorithm: (_a = kdfConfig === null || kdfConfig === void 0 ? void 0 : kdfConfig.algorithm) !== null && _a !== void 0 ? _a : 'argon2id',
520
+ iterations: (_b = kdfConfig === null || kdfConfig === void 0 ? void 0 : kdfConfig.iterations) !== null && _b !== void 0 ? _b : exports.ARGON2ID_DEFAULT_ITERATIONS,
521
+ memoryKiB: (_c = kdfConfig === null || kdfConfig === void 0 ? void 0 : kdfConfig.memoryKiB) !== null && _c !== void 0 ? _c : exports.ARGON2ID_DEFAULT_MEMORY_KIB,
522
+ parallelism: (_d = kdfConfig === null || kdfConfig === void 0 ? void 0 : kdfConfig.parallelism) !== null && _d !== void 0 ? _d : exports.ARGON2ID_DEFAULT_PARALLELISM,
523
+ hashLength: (_e = kdfConfig === null || kdfConfig === void 0 ? void 0 : kdfConfig.hashLength) !== null && _e !== void 0 ? _e : exports.ARGON2ID_DEFAULT_HASH_LENGTH
524
+ };
364
525
  // If a saved snapshot is provided, attempt to load it.
365
526
  // Note: loadSnapshot now returns a promise. We don't await it here,
366
527
  // as the constructor must be synchronous. The caller should check
@@ -413,7 +574,8 @@ class CWIStyleWalletManager {
413
574
  if (!this.currentUMPToken) {
414
575
  throw new Error('Provide presentation or recovery key first.');
415
576
  }
416
- const derivedPasswordKey = await pbkdf2NativeOrJs(sdk_1.Utils.toArray(password, 'utf8'), this.currentUMPToken.passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
577
+ // Use token-driven KDF (legacy PBKDF2 or v3 Argon2id)
578
+ const derivedPasswordKey = await derivePasswordKey(this.currentUMPToken, sdk_1.Utils.toArray(password, 'utf8'));
417
579
  let rootPrimaryKey;
418
580
  let rootPrivilegedKey; // Only needed for recovery mode
419
581
  if (this.authenticationMode === 'presentation-key-and-password') {
@@ -447,7 +609,12 @@ class CWIStyleWalletManager {
447
609
  const recoveryKey = (0, sdk_1.Random)(32);
448
610
  await this.recoveryKeySaver(recoveryKey);
449
611
  const passwordSalt = (0, sdk_1.Random)(32);
450
- const passwordKey = await pbkdf2NativeOrJs(sdk_1.Utils.toArray(password, 'utf8'), passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
612
+ // Build temporary token with KDF config for password derivation
613
+ const tempTokenForKdf = {
614
+ passwordSalt,
615
+ passwordKdf: this.kdfConfig
616
+ };
617
+ const passwordKey = await derivePasswordKey(tempTokenForKdf, sdk_1.Utils.toArray(password, 'utf8'));
451
618
  const rootPrimaryKey = (0, sdk_1.Random)(32);
452
619
  const rootPrivilegedKey = (0, sdk_1.Random)(32);
453
620
  // Build XOR keys
@@ -457,7 +624,7 @@ class CWIStyleWalletManager {
457
624
  const primaryPassword = new sdk_1.SymmetricKey(this.XOR(rootPrimaryKey, passwordKey));
458
625
  // Temp manager for encryption
459
626
  const tempPrivilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async () => new sdk_1.PrivateKey(rootPrivilegedKey));
460
- // Build new UMP token (no profiles initially)
627
+ // Build new UMP token (v3 with KDF metadata, no profiles initially)
461
628
  const newToken = {
462
629
  passwordSalt,
463
630
  passwordPresentationPrimary: presentationPassword.encrypt(rootPrimaryKey),
@@ -482,7 +649,9 @@ class CWIStyleWalletManager {
482
649
  protocolID: [2, 'admin key wrapping'],
483
650
  keyID: '1'
484
651
  })).ciphertext,
485
- profilesEncrypted: undefined // No profiles yet
652
+ profilesEncrypted: undefined, // No profiles yet
653
+ umpVersion: 3, // UMP protocol version 3
654
+ passwordKdf: this.kdfConfig // KDF metadata for v3
486
655
  };
487
656
  this.currentUMPToken = newToken;
488
657
  // Setup root infrastructure and switch to default profile
@@ -495,7 +664,8 @@ class CWIStyleWalletManager {
495
664
  }
496
665
  catch (e) {
497
666
  console.error('Error funding new wallet:', e);
498
- // Decide if this should halt the process or just log
667
+ const message = e instanceof Error ? e.message : String(e);
668
+ throw new Error(`Failed to fund new wallet before publishing UMP token: ${message}`);
499
669
  }
500
670
  }
501
671
  // Publish the new UMP token *after* potentially funding
@@ -803,11 +973,18 @@ class CWIStyleWalletManager {
803
973
  * Changes the user's password. Re-wraps keys and updates the UMP token.
804
974
  */
805
975
  async changePassword(newPassword) {
976
+ var _a;
806
977
  if (!this.authenticated || !this.currentUMPToken || !this.rootPrimaryKey || !this.rootPrivilegedKeyManager) {
807
978
  throw new Error('Not authenticated or missing required data.');
808
979
  }
809
980
  const passwordSalt = (0, sdk_1.Random)(32);
810
- const newPasswordKey = await pbkdf2NativeOrJs(sdk_1.Utils.toArray(newPassword, 'utf8'), passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
981
+ // Preserve current token's KDF metadata (or use manager's kdfConfig for legacy tokens)
982
+ const kdfToUse = (_a = this.currentUMPToken.passwordKdf) !== null && _a !== void 0 ? _a : this.kdfConfig;
983
+ const tempTokenForKdf = {
984
+ passwordSalt,
985
+ passwordKdf: kdfToUse
986
+ };
987
+ const newPasswordKey = await derivePasswordKey(tempTokenForKdf, sdk_1.Utils.toArray(newPassword, 'utf8'));
811
988
  // Decrypt existing factors needed for re-encryption, using the *root* privileged key manager
812
989
  const recoveryKey = await this.getFactor('recoveryKey');
813
990
  const presentationKey = await this.getFactor('presentationKey');
@@ -936,6 +1113,7 @@ class CWIStyleWalletManager {
936
1113
  async updateAuthFactors(passwordSalt, passwordKey, presentationKey, recoveryKey, rootPrimaryKey, rootPrivilegedKey, // Explicitly pass the root key bytes
937
1114
  profiles // Pass current/new profiles list
938
1115
  ) {
1116
+ var _a;
939
1117
  if (!this.authenticated || !this.rootPrimaryKey || !this.currentUMPToken) {
940
1118
  throw new Error('Wallet is not properly authenticated or missing data for update.');
941
1119
  }
@@ -958,7 +1136,8 @@ class CWIStyleWalletManager {
958
1136
  const profilesBytes = sdk_1.Utils.toArray(profilesJson, 'utf8');
959
1137
  profilesEncrypted = new sdk_1.SymmetricKey(rootPrimaryKey).encrypt(profilesBytes);
960
1138
  }
961
- // Construct the new UMP token data
1139
+ // Construct the new UMP token data (preserve or upgrade to v3 with KDF metadata)
1140
+ const kdfMetadata = (_a = this.currentUMPToken.passwordKdf) !== null && _a !== void 0 ? _a : this.kdfConfig;
962
1141
  const newTokenData = {
963
1142
  passwordSalt,
964
1143
  passwordPresentationPrimary: presentationPassword.encrypt(rootPrimaryKey),
@@ -983,7 +1162,9 @@ class CWIStyleWalletManager {
983
1162
  protocolID: [2, 'admin key wrapping'],
984
1163
  keyID: '1'
985
1164
  })).ciphertext,
986
- profilesEncrypted // Add encrypted profiles
1165
+ profilesEncrypted, // Add encrypted profiles
1166
+ umpVersion: 3, // UMP protocol version 3
1167
+ passwordKdf: kdfMetadata // Preserve or upgrade KDF metadata
987
1168
  // currentOutpoint will be set after publishing
988
1169
  };
989
1170
  // We need the wallet built for the DEFAULT profile to publish the UMP token.
@@ -1015,15 +1196,16 @@ class CWIStyleWalletManager {
1015
1196
  }
1016
1197
  }
1017
1198
  /**
1018
- * Serializes a UMP token to binary format (Version 2 with optional profiles).
1019
- * Layout: [1 byte version=2] + [11 * (varint len + bytes) for standard fields] + [1 byte profile_flag] + [IF flag=1 THEN varint len + profile bytes] + [varint len + outpoint bytes]
1199
+ * Serializes a UMP token to binary format (Version 3 with KDF metadata, Version 2 with profiles).
1200
+ * V3 Layout: [1 byte version=3] + [11 * (varint len + bytes) for standard fields] + [1 byte profile_flag] + [IF flag=1 THEN varint len + profile bytes] + [1 byte kdf_flag] + [IF flag=1 THEN kdf metadata] + [varint len + outpoint bytes]
1020
1201
  */
1021
1202
  serializeUMPToken(token) {
1022
1203
  if (!token.currentOutpoint) {
1023
1204
  throw new Error('Token must have outpoint for serialization');
1024
1205
  }
1025
1206
  const writer = new sdk_1.Utils.Writer();
1026
- writer.writeUInt8(2); // Version 2
1207
+ const hasKdfMetadata = token.umpVersion === 3 && token.passwordKdf;
1208
+ writer.writeUInt8(hasKdfMetadata ? 3 : 2); // Version 3 for KDF, 2 for legacy
1027
1209
  const writeArray = (arr) => {
1028
1210
  writer.writeVarIntNum(arr.length);
1029
1211
  writer.write(arr);
@@ -1038,7 +1220,7 @@ class CWIStyleWalletManager {
1038
1220
  writeArray(token.presentationHash); // 6
1039
1221
  writeArray(token.recoveryHash); // 7
1040
1222
  writeArray(token.presentationKeyEncrypted); // 8
1041
- writeArray(token.passwordKeyEncrypted); // 9 - Swapped order vs original doc comment
1223
+ writeArray(token.passwordKeyEncrypted); // 9
1042
1224
  writeArray(token.recoveryKeyEncrypted); // 10
1043
1225
  // Write optional profiles field
1044
1226
  if (token.profilesEncrypted && token.profilesEncrypted.length > 0) {
@@ -1048,6 +1230,32 @@ class CWIStyleWalletManager {
1048
1230
  else {
1049
1231
  writer.writeUInt8(0); // Flag indicating no profiles
1050
1232
  }
1233
+ // V3: Write KDF metadata
1234
+ if (hasKdfMetadata) {
1235
+ writer.writeUInt8(1); // Flag indicating KDF metadata present
1236
+ writer.writeUInt8(token.umpVersion); // On-chain UMP version
1237
+ const algorithmBytes = sdk_1.Utils.toArray(token.passwordKdf.algorithm, 'utf8');
1238
+ writeArray(algorithmBytes);
1239
+ // Serialize KDF params as JSON
1240
+ const kdfParams = {
1241
+ iterations: token.passwordKdf.iterations
1242
+ };
1243
+ if (token.passwordKdf.memoryKiB !== undefined) {
1244
+ kdfParams.memoryKiB = token.passwordKdf.memoryKiB;
1245
+ }
1246
+ if (token.passwordKdf.parallelism !== undefined) {
1247
+ kdfParams.parallelism = token.passwordKdf.parallelism;
1248
+ }
1249
+ if (token.passwordKdf.hashLength !== undefined) {
1250
+ kdfParams.hashLength = token.passwordKdf.hashLength;
1251
+ }
1252
+ const kdfParamsBytes = sdk_1.Utils.toArray(JSON.stringify(kdfParams), 'utf8');
1253
+ writeArray(kdfParamsBytes);
1254
+ }
1255
+ else if (writer.toArray()[0] === 3) {
1256
+ // Version 3 without KDF metadata (shouldn't happen, but handle gracefully)
1257
+ writer.writeUInt8(0); // Flag indicating no KDF metadata
1258
+ }
1051
1259
  // Write outpoint string
1052
1260
  const outpointBytes = sdk_1.Utils.toArray(token.currentOutpoint, 'utf8');
1053
1261
  writer.writeVarIntNum(outpointBytes.length);
@@ -1055,19 +1263,19 @@ class CWIStyleWalletManager {
1055
1263
  return writer.toArray();
1056
1264
  }
1057
1265
  /**
1058
- * Deserializes a UMP token from binary format (Handles Version 1 and 2).
1266
+ * Deserializes a UMP token from binary format (Handles Version 1, 2, and 3).
1059
1267
  */
1060
1268
  deserializeUMPToken(bin) {
1061
1269
  const reader = new sdk_1.Utils.Reader(bin);
1062
1270
  const version = reader.readUInt8();
1063
- if (version !== 1 && version !== 2) {
1271
+ if (version !== 1 && version !== 2 && version !== 3) {
1064
1272
  throw new Error(`Unsupported UMP token serialization version: ${version}`);
1065
1273
  }
1066
1274
  const readArray = () => {
1067
1275
  const length = reader.readVarIntNum();
1068
1276
  return reader.read(length);
1069
1277
  };
1070
- // Read standard fields (order matches serialization V2)
1278
+ // Read standard fields (order matches serialization)
1071
1279
  const passwordSalt = readArray(); // 0
1072
1280
  const passwordPresentationPrimary = readArray(); // 1
1073
1281
  const passwordRecoveryPrimary = readArray(); // 2
@@ -1079,14 +1287,40 @@ class CWIStyleWalletManager {
1079
1287
  const presentationKeyEncrypted = readArray(); // 8
1080
1288
  const passwordKeyEncrypted = readArray(); // 9
1081
1289
  const recoveryKeyEncrypted = readArray(); // 10
1082
- // Read optional profiles (only in V2)
1290
+ // Read optional profiles (V2 and V3)
1083
1291
  let profilesEncrypted;
1084
- if (version === 2) {
1292
+ if (version >= 2) {
1085
1293
  const profilesFlag = reader.readUInt8();
1086
1294
  if (profilesFlag === 1) {
1087
1295
  profilesEncrypted = readArray();
1088
1296
  }
1089
1297
  }
1298
+ // Read KDF metadata (V3 only)
1299
+ let umpVersion;
1300
+ let passwordKdf;
1301
+ if (version === 3) {
1302
+ const kdfFlag = reader.readUInt8();
1303
+ if (kdfFlag === 1) {
1304
+ umpVersion = reader.readUInt8(); // On-chain UMP version
1305
+ const algorithmBytes = readArray();
1306
+ const algorithm = sdk_1.Utils.toUTF8(algorithmBytes);
1307
+ const kdfParamsBytes = readArray();
1308
+ const kdfParamsJson = sdk_1.Utils.toUTF8(kdfParamsBytes);
1309
+ try {
1310
+ const kdfParams = JSON.parse(kdfParamsJson);
1311
+ passwordKdf = {
1312
+ algorithm,
1313
+ iterations: kdfParams.iterations,
1314
+ memoryKiB: kdfParams.memoryKiB,
1315
+ parallelism: kdfParams.parallelism,
1316
+ hashLength: kdfParams.hashLength
1317
+ };
1318
+ }
1319
+ catch (e) {
1320
+ console.warn('Failed to parse KDF params during deserialization:', e);
1321
+ }
1322
+ }
1323
+ }
1090
1324
  // Read outpoint string
1091
1325
  const outpointLen = reader.readVarIntNum();
1092
1326
  const outpointBytes = reader.read(outpointLen);
@@ -1101,10 +1335,12 @@ class CWIStyleWalletManager {
1101
1335
  presentationHash,
1102
1336
  recoveryHash,
1103
1337
  presentationKeyEncrypted,
1104
- passwordKeyEncrypted, // Corrected order
1338
+ passwordKeyEncrypted,
1105
1339
  recoveryKeyEncrypted,
1106
- profilesEncrypted, // May be undefined
1107
- currentOutpoint
1340
+ profilesEncrypted,
1341
+ currentOutpoint,
1342
+ umpVersion,
1343
+ passwordKdf
1108
1344
  };
1109
1345
  return token;
1110
1346
  }
@@ -1134,9 +1370,9 @@ class CWIStyleWalletManager {
1134
1370
  return tempKey;
1135
1371
  }
1136
1372
  // 2. Otherwise, derive from password
1137
- const password = await this.passwordRetriever(reason, (passwordCandidate) => {
1373
+ const password = await this.passwordRetriever(reason, async (passwordCandidate) => {
1138
1374
  try {
1139
- const derivedPasswordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(passwordCandidate, 'utf8'), this.currentUMPToken.passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
1375
+ const derivedPasswordKey = await derivePasswordKey(this.currentUMPToken, sdk_1.Utils.toArray(passwordCandidate, 'utf8'));
1140
1376
  const privilegedDecryptor = this.XOR(this.rootPrimaryKey, derivedPasswordKey);
1141
1377
  const decryptedPrivileged = new sdk_1.SymmetricKey(privilegedDecryptor).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
1142
1378
  return !!decryptedPrivileged; // Test passes if decryption works
@@ -1145,8 +1381,8 @@ class CWIStyleWalletManager {
1145
1381
  return false;
1146
1382
  }
1147
1383
  });
1148
- // Decrypt the root privileged key using the confirmed password
1149
- const derivedPasswordKey = await pbkdf2NativeOrJs(sdk_1.Utils.toArray(password, 'utf8'), this.currentUMPToken.passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
1384
+ // Decrypt the root privileged key using the confirmed password (with token-driven KDF)
1385
+ const derivedPasswordKey = await derivePasswordKey(this.currentUMPToken, sdk_1.Utils.toArray(password, 'utf8'));
1150
1386
  const privilegedDecryptor = this.XOR(this.rootPrimaryKey, derivedPasswordKey);
1151
1387
  const rootPrivilegedBytes = new sdk_1.SymmetricKey(privilegedDecryptor).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
1152
1388
  return new sdk_1.PrivateKey(rootPrivilegedBytes); // Return the ROOT key object