@bsv/wallet-toolbox 1.2.36 → 1.2.37
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/.github/pull_request_template.md +0 -2
- package/docs/client.md +244 -169
- package/docs/wallet.md +244 -169
- package/out/src/CWIStyleWalletManager.d.ts +151 -131
- package/out/src/CWIStyleWalletManager.d.ts.map +1 -1
- package/out/src/CWIStyleWalletManager.js +654 -555
- package/out/src/CWIStyleWalletManager.js.map +1 -1
- package/out/src/__tests/CWIStyleWalletManager.test.js +6 -6
- package/out/src/__tests/CWIStyleWalletManager.test.js.map +1 -1
- package/out/test/Wallet/local/localWallet.man.test.js.map +1 -1
- package/out/test/Wallet/local/localWallet2.man.test.js +10 -1
- package/out/test/Wallet/local/localWallet2.man.test.js.map +1 -1
- package/out/test/Wallet/support/opers1.man.test.js +6 -4
- package/out/test/Wallet/support/opers1.man.test.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/CWIStyleWalletManager.ts +821 -663
- package/src/__tests/CWIStyleWalletManager.test.ts +6 -6
- package/test/Wallet/local/localWallet.man.test.ts +1 -5
- package/test/Wallet/local/localWallet2.man.test.ts +13 -3
- package/test/Wallet/support/opers1.man.test.ts +26 -21
|
@@ -70,32 +70,67 @@ import { PrivilegedKeyManager } from './sdk/PrivilegedKeyManager'
|
|
|
70
70
|
*/
|
|
71
71
|
export const PBKDF2_NUM_ROUNDS = 7777
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Unique Identifier for the default profile (16 zero bytes).
|
|
75
|
+
*/
|
|
76
|
+
export const DEFAULT_PROFILE_ID = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Describes the structure of a user profile within the wallet.
|
|
80
|
+
*/
|
|
81
|
+
export interface Profile {
|
|
82
|
+
/**
|
|
83
|
+
* User-defined name for the profile.
|
|
84
|
+
*/
|
|
85
|
+
name: string
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Unique 16-byte identifier for the profile.
|
|
89
|
+
*/
|
|
90
|
+
id: number[]
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 32-byte random pad XOR'd with the root primary key to derive the profile's primary key.
|
|
94
|
+
*/
|
|
95
|
+
primaryPad: number[]
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 32-byte random pad XOR'd with the root privileged key to derive the profile's privileged key.
|
|
99
|
+
*/
|
|
100
|
+
privilegedPad: number[]
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Timestamp (seconds since epoch) when the profile was created.
|
|
104
|
+
*/
|
|
105
|
+
createdAt: number
|
|
106
|
+
}
|
|
107
|
+
|
|
73
108
|
/**
|
|
74
109
|
* Describes the structure of a User Management Protocol (UMP) token.
|
|
75
110
|
*/
|
|
76
111
|
export interface UMPToken {
|
|
77
112
|
/**
|
|
78
|
-
* Primary key encrypted by the XOR of the password and presentation keys.
|
|
113
|
+
* Root Primary key encrypted by the XOR of the password and presentation keys.
|
|
79
114
|
*/
|
|
80
115
|
passwordPresentationPrimary: number[]
|
|
81
116
|
|
|
82
117
|
/**
|
|
83
|
-
* Primary key encrypted by the XOR of the password and recovery keys.
|
|
118
|
+
* Root Primary key encrypted by the XOR of the password and recovery keys.
|
|
84
119
|
*/
|
|
85
120
|
passwordRecoveryPrimary: number[]
|
|
86
121
|
|
|
87
122
|
/**
|
|
88
|
-
* Primary key encrypted by the XOR of the presentation and recovery keys.
|
|
123
|
+
* Root Primary key encrypted by the XOR of the presentation and recovery keys.
|
|
89
124
|
*/
|
|
90
125
|
presentationRecoveryPrimary: number[]
|
|
91
126
|
|
|
92
127
|
/**
|
|
93
|
-
* Privileged key encrypted by the XOR of the password and primary keys.
|
|
128
|
+
* Root Privileged key encrypted by the XOR of the password and primary keys.
|
|
94
129
|
*/
|
|
95
130
|
passwordPrimaryPrivileged: number[]
|
|
96
131
|
|
|
97
132
|
/**
|
|
98
|
-
* Privileged key encrypted by the XOR of the presentation and recovery keys.
|
|
133
|
+
* Root Privileged key encrypted by the XOR of the presentation and recovery keys.
|
|
99
134
|
*/
|
|
100
135
|
presentationRecoveryPrivileged: number[]
|
|
101
136
|
|
|
@@ -115,20 +150,26 @@ export interface UMPToken {
|
|
|
115
150
|
recoveryHash: number[]
|
|
116
151
|
|
|
117
152
|
/**
|
|
118
|
-
* A copy of the presentation key encrypted with the privileged key.
|
|
153
|
+
* A copy of the presentation key encrypted with the root privileged key.
|
|
119
154
|
*/
|
|
120
155
|
presentationKeyEncrypted: number[]
|
|
121
156
|
|
|
122
157
|
/**
|
|
123
|
-
* A copy of the recovery key encrypted with the privileged key.
|
|
158
|
+
* A copy of the recovery key encrypted with the root privileged key.
|
|
124
159
|
*/
|
|
125
160
|
recoveryKeyEncrypted: number[]
|
|
126
161
|
|
|
127
162
|
/**
|
|
128
|
-
* A copy of the password key encrypted with the privileged key.
|
|
163
|
+
* A copy of the password key encrypted with the root privileged key.
|
|
129
164
|
*/
|
|
130
165
|
passwordKeyEncrypted: number[]
|
|
131
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Optional field containing the encrypted profile data.
|
|
169
|
+
* JSON string -> Encrypted Bytes using root privileged key.
|
|
170
|
+
*/
|
|
171
|
+
profilesEncrypted?: number[]
|
|
172
|
+
|
|
132
173
|
/**
|
|
133
174
|
* Describes the token's location on-chain, if it's already been published.
|
|
134
175
|
*/
|
|
@@ -160,14 +201,14 @@ export interface UMPTokenInteractor {
|
|
|
160
201
|
/**
|
|
161
202
|
* Creates (and optionally consumes the previous version of) a UMP token on-chain.
|
|
162
203
|
*
|
|
163
|
-
* @param wallet The wallet that might be used to create a new token.
|
|
204
|
+
* @param wallet The wallet that might be used to create a new token (MUST be operating under the DEFAULT profile).
|
|
164
205
|
* @param adminOriginator The domain name of the administrative originator.
|
|
165
206
|
* @param token The new UMP token to create.
|
|
166
207
|
* @param oldTokenToConsume If provided, the old token that must be consumed in the same transaction.
|
|
167
208
|
* @returns The newly created outpoint.
|
|
168
209
|
*/
|
|
169
210
|
buildAndSend: (
|
|
170
|
-
wallet: WalletInterface,
|
|
211
|
+
wallet: WalletInterface, // This wallet MUST be the one built for the default profile
|
|
171
212
|
adminOriginator: OriginatorDomainNameStringUnder250Bytes,
|
|
172
213
|
token: UMPToken,
|
|
173
214
|
oldTokenToConsume?: UMPToken
|
|
@@ -251,34 +292,20 @@ export class OverlayUMPTokenInteractor implements UMPTokenInteractor {
|
|
|
251
292
|
* then broadcast and published under the `tm_users` topic using a SHIP broadcast, ensuring
|
|
252
293
|
* overlay participants see the updated token.
|
|
253
294
|
*
|
|
254
|
-
* @param wallet The wallet used to build and sign the transaction.
|
|
295
|
+
* @param wallet The wallet used to build and sign the transaction (MUST be operating under the DEFAULT profile).
|
|
255
296
|
* @param adminOriginator The domain/FQDN of the administrative originator (wallet operator).
|
|
256
297
|
* @param token The new UMPToken to create on-chain.
|
|
257
298
|
* @param oldTokenToConsume Optionally, an existing token to consume/spend in the same transaction.
|
|
258
299
|
* @returns The outpoint of the newly created UMP token (e.g. "abcd1234...ef.0").
|
|
259
300
|
*/
|
|
260
301
|
public async buildAndSend(
|
|
261
|
-
wallet: WalletInterface,
|
|
302
|
+
wallet: WalletInterface, // This wallet MUST be the one built for the default profile
|
|
262
303
|
adminOriginator: OriginatorDomainNameStringUnder250Bytes,
|
|
263
304
|
token: UMPToken,
|
|
264
305
|
oldTokenToConsume?: UMPToken
|
|
265
306
|
): Promise<OutpointString> {
|
|
266
|
-
// 1) Construct the data fields for the new UMP token
|
|
267
|
-
|
|
268
|
-
const fields: number[][] = new Array(11)
|
|
269
|
-
|
|
270
|
-
// See: UMP field ordering
|
|
271
|
-
// 0 => passwordSalt
|
|
272
|
-
// 1 => passwordPresentationPrimary
|
|
273
|
-
// 2 => passwordRecoveryPrimary
|
|
274
|
-
// 3 => presentationRecoveryPrimary
|
|
275
|
-
// 4 => passwordPrimaryPrivileged
|
|
276
|
-
// 5 => presentationRecoveryPrivileged
|
|
277
|
-
// 6 => presentationHash
|
|
278
|
-
// 7 => recoveryHash
|
|
279
|
-
// 8 => presentationKeyEncrypted
|
|
280
|
-
// 9 => passwordKeyEncrypted
|
|
281
|
-
// 10 => recoveryKeyEncrypted
|
|
307
|
+
// 1) Construct the data fields for the new UMP token.
|
|
308
|
+
const fields: number[][] = []
|
|
282
309
|
|
|
283
310
|
fields[0] = token.passwordSalt
|
|
284
311
|
fields[1] = token.passwordPresentationPrimary
|
|
@@ -292,7 +319,12 @@ export class OverlayUMPTokenInteractor implements UMPTokenInteractor {
|
|
|
292
319
|
fields[9] = token.passwordKeyEncrypted
|
|
293
320
|
fields[10] = token.recoveryKeyEncrypted
|
|
294
321
|
|
|
295
|
-
//
|
|
322
|
+
// Optional field (11) for encrypted profiles
|
|
323
|
+
if (token.profilesEncrypted) {
|
|
324
|
+
fields[11] = token.profilesEncrypted
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 2) Create a PushDrop script referencing these fields, locked with the admin key.
|
|
296
328
|
const script = await new PushDrop(wallet, adminOriginator).lock(
|
|
297
329
|
fields,
|
|
298
330
|
[2, 'admin user management token'], // protocolID
|
|
@@ -302,7 +334,7 @@ export class OverlayUMPTokenInteractor implements UMPTokenInteractor {
|
|
|
302
334
|
/*includeSignature=*/ true
|
|
303
335
|
)
|
|
304
336
|
|
|
305
|
-
// 3) Prepare the createAction call. If oldTokenToConsume is provided,
|
|
337
|
+
// 3) Prepare the createAction call. If oldTokenToConsume is provided, gather the outpoint.
|
|
306
338
|
const inputs: CreateActionInput[] = []
|
|
307
339
|
let inputToken: { beef: number[]; outputIndex: number } | undefined
|
|
308
340
|
if (oldTokenToConsume?.currentOutpoint) {
|
|
@@ -333,8 +365,7 @@ export class OverlayUMPTokenInteractor implements UMPTokenInteractor {
|
|
|
333
365
|
adminOriginator
|
|
334
366
|
)
|
|
335
367
|
|
|
336
|
-
// If the transaction is fully processed by the wallet
|
|
337
|
-
// we retrieve the final TXID from the result.
|
|
368
|
+
// If the transaction is fully processed by the wallet
|
|
338
369
|
if (!createResult.signableTransaction) {
|
|
339
370
|
const finalTxid =
|
|
340
371
|
createResult.txid || (createResult.tx ? Transaction.fromAtomicBEEF(createResult.tx).id('hex') : undefined)
|
|
@@ -377,19 +408,25 @@ export class OverlayUMPTokenInteractor implements UMPTokenInteractor {
|
|
|
377
408
|
}
|
|
378
409
|
// 6) Broadcast to `tm_users`
|
|
379
410
|
const finalAtomicTx = signResult.tx
|
|
380
|
-
|
|
411
|
+
if (!finalAtomicTx) {
|
|
412
|
+
throw new Error('Final transaction data missing after signing renewed UMP token.')
|
|
413
|
+
}
|
|
414
|
+
const broadcastTx = Transaction.fromAtomicBEEF(finalAtomicTx)
|
|
381
415
|
const result = await this.broadcaster.broadcast(broadcastTx)
|
|
382
416
|
console.log('BROADCAST RESULT', result)
|
|
383
417
|
return `${finalTxid}.0`
|
|
384
418
|
} else {
|
|
385
|
-
//
|
|
419
|
+
// Fallback for creating a new token (no input spending)
|
|
386
420
|
const signResult = await wallet.signAction({ reference, spends: {} }, adminOriginator)
|
|
387
421
|
finalTxid = signResult.txid || (signResult.tx ? Transaction.fromAtomicBEEF(signResult.tx).id('hex') : '')
|
|
388
422
|
if (!finalTxid) {
|
|
389
423
|
throw new Error('Failed to finalize new UMP token transaction.')
|
|
390
424
|
}
|
|
391
425
|
const finalAtomicTx = signResult.tx
|
|
392
|
-
|
|
426
|
+
if (!finalAtomicTx) {
|
|
427
|
+
throw new Error('Final transaction data missing after signing new UMP token.')
|
|
428
|
+
}
|
|
429
|
+
const broadcastTx = Transaction.fromAtomicBEEF(finalAtomicTx)
|
|
393
430
|
const result = await this.broadcaster.broadcast(broadcastTx)
|
|
394
431
|
console.log('BROADCAST RESULT', result)
|
|
395
432
|
return `${finalTxid}.0`
|
|
@@ -412,8 +449,6 @@ export class OverlayUMPTokenInteractor implements UMPTokenInteractor {
|
|
|
412
449
|
return undefined
|
|
413
450
|
}
|
|
414
451
|
|
|
415
|
-
// We expect only one relevant UMP token in most queries, so let's parse the first.
|
|
416
|
-
// If multiple are returned, we can parse the first.
|
|
417
452
|
const { beef, outputIndex } = answer.outputs[0]
|
|
418
453
|
try {
|
|
419
454
|
const tx = Transaction.fromBEEF(beef)
|
|
@@ -421,11 +456,15 @@ export class OverlayUMPTokenInteractor implements UMPTokenInteractor {
|
|
|
421
456
|
|
|
422
457
|
const decoded = PushDrop.decode(tx.outputs[outputIndex].lockingScript)
|
|
423
458
|
|
|
424
|
-
// Expecting 11 fields for UMP
|
|
425
|
-
if (!decoded.fields || decoded.fields.length < 11)
|
|
459
|
+
// Expecting 11 or more fields for UMP
|
|
460
|
+
if (!decoded.fields || decoded.fields.length < 11) {
|
|
461
|
+
console.warn(`Unexpected number of fields in UMP token: ${decoded.fields?.length}`)
|
|
462
|
+
return undefined
|
|
463
|
+
}
|
|
426
464
|
|
|
427
465
|
// Build the UMP token from these fields, preserving outpoint
|
|
428
466
|
const t: UMPToken = {
|
|
467
|
+
// Order matches buildAndSend and serialize/deserialize
|
|
429
468
|
passwordSalt: decoded.fields[0],
|
|
430
469
|
passwordPresentationPrimary: decoded.fields[1],
|
|
431
470
|
passwordRecoveryPrimary: decoded.fields[2],
|
|
@@ -437,11 +476,12 @@ export class OverlayUMPTokenInteractor implements UMPTokenInteractor {
|
|
|
437
476
|
presentationKeyEncrypted: decoded.fields[8],
|
|
438
477
|
passwordKeyEncrypted: decoded.fields[9],
|
|
439
478
|
recoveryKeyEncrypted: decoded.fields[10],
|
|
479
|
+
profilesEncrypted: decoded.fields[12] ? decoded.fields[11] : undefined, // If there's a signature in field 12, use field 11
|
|
440
480
|
currentOutpoint: outpoint
|
|
441
481
|
}
|
|
442
482
|
return t
|
|
443
483
|
} catch (e) {
|
|
444
|
-
|
|
484
|
+
console.error('Failed to parse or decode UMP token:', e)
|
|
445
485
|
return undefined
|
|
446
486
|
}
|
|
447
487
|
}
|
|
@@ -461,7 +501,7 @@ export class OverlayUMPTokenInteractor implements UMPTokenInteractor {
|
|
|
461
501
|
if (results.type !== 'output-list') {
|
|
462
502
|
return undefined
|
|
463
503
|
}
|
|
464
|
-
if (!results.outputs.length) {
|
|
504
|
+
if (!results.outputs || !results.outputs.length) {
|
|
465
505
|
return undefined
|
|
466
506
|
}
|
|
467
507
|
return results.outputs[0]
|
|
@@ -470,18 +510,19 @@ export class OverlayUMPTokenInteractor implements UMPTokenInteractor {
|
|
|
470
510
|
|
|
471
511
|
/**
|
|
472
512
|
* Manages a "CWI-style" wallet that uses a UMP token and a
|
|
473
|
-
* multi-key authentication scheme (password, presentation key, and recovery key)
|
|
513
|
+
* multi-key authentication scheme (password, presentation key, and recovery key),
|
|
514
|
+
* supporting multiple user profiles under a single account.
|
|
474
515
|
*/
|
|
475
516
|
export class CWIStyleWalletManager implements WalletInterface {
|
|
476
517
|
/**
|
|
477
|
-
* Whether the user is currently authenticated.
|
|
518
|
+
* Whether the user is currently authenticated (i.e., root keys are available).
|
|
478
519
|
*/
|
|
479
520
|
authenticated: boolean
|
|
480
521
|
|
|
481
522
|
/**
|
|
482
523
|
* The domain name of the administrative originator (wallet operator / vendor, or your own).
|
|
483
524
|
*/
|
|
484
|
-
private adminOriginator:
|
|
525
|
+
private adminOriginator: OriginatorDomainNameStringUnder250Bytes
|
|
485
526
|
|
|
486
527
|
/**
|
|
487
528
|
* The system that locates and publishes UMP tokens on-chain.
|
|
@@ -503,26 +544,25 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
503
544
|
private passwordRetriever: (reason: string, test: (passwordCandidate: string) => boolean) => Promise<string>
|
|
504
545
|
|
|
505
546
|
/**
|
|
506
|
-
*
|
|
507
|
-
* Allows integration with faucets, and provides the presentation key for use in claiming faucet funds
|
|
508
|
-
* that may be bound to it.
|
|
547
|
+
* Optional function to fund a new Wallet after the new-user flow.
|
|
509
548
|
*/
|
|
510
549
|
private newWalletFunder?: (
|
|
511
550
|
presentationKey: number[],
|
|
512
|
-
wallet: WalletInterface,
|
|
551
|
+
wallet: WalletInterface, // The default profile wallet
|
|
513
552
|
adminOriginator: OriginatorDomainNameStringUnder250Bytes
|
|
514
553
|
) => Promise<void>
|
|
515
554
|
|
|
516
555
|
/**
|
|
517
|
-
* Builds the underlying wallet
|
|
556
|
+
* Builds the underlying wallet for a specific profile.
|
|
518
557
|
*/
|
|
519
|
-
private walletBuilder: (
|
|
558
|
+
private walletBuilder: (
|
|
559
|
+
profilePrimaryKey: number[],
|
|
560
|
+
profilePrivilegedKeyManager: PrivilegedKeyManager,
|
|
561
|
+
profileId: number[]
|
|
562
|
+
) => Promise<WalletInterface>
|
|
520
563
|
|
|
521
564
|
/**
|
|
522
|
-
*
|
|
523
|
-
* - 'presentation-key-and-password'
|
|
524
|
-
* - 'presentation-key-and-recovery-key'
|
|
525
|
-
* - 'recovery-key-and-password'
|
|
565
|
+
* Current mode of authentication.
|
|
526
566
|
*/
|
|
527
567
|
authenticationMode:
|
|
528
568
|
| 'presentation-key-and-password'
|
|
@@ -530,66 +570,74 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
530
570
|
| 'recovery-key-and-password' = 'presentation-key-and-password'
|
|
531
571
|
|
|
532
572
|
/**
|
|
533
|
-
* Indicates
|
|
534
|
-
* - 'new-user'
|
|
535
|
-
* - 'existing-user'
|
|
573
|
+
* Indicates new user or existing user flow.
|
|
536
574
|
*/
|
|
537
575
|
authenticationFlow: 'new-user' | 'existing-user' = 'new-user'
|
|
538
576
|
|
|
539
577
|
/**
|
|
540
|
-
* The current UMP token in use
|
|
578
|
+
* The current UMP token in use.
|
|
541
579
|
*/
|
|
542
580
|
private currentUMPToken?: UMPToken
|
|
543
581
|
|
|
544
582
|
/**
|
|
545
|
-
*
|
|
583
|
+
* Temporarily retained presentation key.
|
|
546
584
|
*/
|
|
547
585
|
private presentationKey?: number[]
|
|
548
586
|
|
|
549
587
|
/**
|
|
550
|
-
*
|
|
588
|
+
* Temporarily retained recovery key.
|
|
551
589
|
*/
|
|
552
590
|
private recoveryKey?: number[]
|
|
553
591
|
|
|
554
592
|
/**
|
|
555
|
-
* The user's primary key,
|
|
556
|
-
|
|
593
|
+
* The user's *root* primary key, derived from authentication factors.
|
|
594
|
+
*/
|
|
595
|
+
private rootPrimaryKey?: number[]
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* The currently active profile ID (null or DEFAULT_PROFILE_ID means default profile).
|
|
599
|
+
*/
|
|
600
|
+
private activeProfileId: number[] = DEFAULT_PROFILE_ID
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* List of loaded non-default profiles.
|
|
557
604
|
*/
|
|
558
|
-
private
|
|
605
|
+
private profiles: Profile[] = []
|
|
559
606
|
|
|
560
607
|
/**
|
|
561
|
-
* The underlying wallet
|
|
562
|
-
* actual signing, encryption, and other wallet operations.
|
|
608
|
+
* The underlying wallet instance for the *active* profile.
|
|
563
609
|
*/
|
|
564
610
|
private underlying?: WalletInterface
|
|
565
611
|
|
|
566
612
|
/**
|
|
567
|
-
* Privileged key manager associated with the
|
|
568
|
-
* short-term administrative tasks (e.g. re-wrapping or rotating keys).
|
|
613
|
+
* Privileged key manager associated with the *root* keys, aware of the active profile.
|
|
569
614
|
*/
|
|
570
|
-
private
|
|
615
|
+
private rootPrivilegedKeyManager?: PrivilegedKeyManager
|
|
571
616
|
|
|
572
617
|
/**
|
|
573
618
|
* Constructs a new CWIStyleWalletManager.
|
|
574
619
|
*
|
|
575
620
|
* @param adminOriginator The domain name of the administrative originator.
|
|
576
|
-
* @param walletBuilder A function that can build an underlying wallet instance
|
|
577
|
-
*
|
|
578
|
-
* @param
|
|
579
|
-
* @param
|
|
580
|
-
* @param
|
|
581
|
-
* @param
|
|
582
|
-
* @param stateSnapshot If provided, a previously saved snapshot of the wallet's state.
|
|
621
|
+
* @param walletBuilder A function that can build an underlying wallet instance for a profile.
|
|
622
|
+
* @param interactor An instance of UMPTokenInteractor.
|
|
623
|
+
* @param recoveryKeySaver A function to persist a new recovery key.
|
|
624
|
+
* @param passwordRetriever A function to request the user's password.
|
|
625
|
+
* @param newWalletFunder Optional function to fund a new wallet.
|
|
626
|
+
* @param stateSnapshot Optional previously saved state snapshot.
|
|
583
627
|
*/
|
|
584
628
|
constructor(
|
|
585
629
|
adminOriginator: OriginatorDomainNameStringUnder250Bytes,
|
|
586
|
-
walletBuilder: (
|
|
630
|
+
walletBuilder: (
|
|
631
|
+
profilePrimaryKey: number[],
|
|
632
|
+
profilePrivilegedKeyManager: PrivilegedKeyManager,
|
|
633
|
+
profileId: number[]
|
|
634
|
+
) => Promise<WalletInterface>,
|
|
587
635
|
interactor: UMPTokenInteractor = new OverlayUMPTokenInteractor(),
|
|
588
636
|
recoveryKeySaver: (key: number[]) => Promise<true>,
|
|
589
637
|
passwordRetriever: (reason: string, test: (passwordCandidate: string) => boolean) => Promise<string>,
|
|
590
638
|
newWalletFunder?: (
|
|
591
639
|
presentationKey: number[],
|
|
592
|
-
wallet: WalletInterface,
|
|
640
|
+
wallet: WalletInterface, // Default profile wallet
|
|
593
641
|
adminOriginator: OriginatorDomainNameStringUnder250Bytes
|
|
594
642
|
) => Promise<void>,
|
|
595
643
|
stateSnapshot?: number[]
|
|
@@ -603,18 +651,22 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
603
651
|
this.newWalletFunder = newWalletFunder
|
|
604
652
|
|
|
605
653
|
// If a saved snapshot is provided, attempt to load it.
|
|
654
|
+
// Note: loadSnapshot now returns a promise. We don't await it here,
|
|
655
|
+
// as the constructor must be synchronous. The caller should check
|
|
656
|
+
// `this.authenticated` after construction if a snapshot was provided.
|
|
606
657
|
if (stateSnapshot) {
|
|
607
|
-
this.loadSnapshot(stateSnapshot)
|
|
658
|
+
this.loadSnapshot(stateSnapshot).catch(err => {
|
|
659
|
+
console.error('Failed to load snapshot during construction:', err)
|
|
660
|
+
// Clear potentially partially loaded state
|
|
661
|
+
this.destroy()
|
|
662
|
+
})
|
|
608
663
|
}
|
|
609
664
|
}
|
|
610
665
|
|
|
666
|
+
// --- Authentication Methods ---
|
|
667
|
+
|
|
611
668
|
/**
|
|
612
|
-
* Provides the presentation key
|
|
613
|
-
* If a UMP token is found based on the key's hash, this is an existing-user flow.
|
|
614
|
-
* Otherwise, it is treated as a new-user flow.
|
|
615
|
-
*
|
|
616
|
-
* @param key The user's presentation key (32 bytes).
|
|
617
|
-
* @throws {Error} if user is already authenticated, or if the current mode does not require a presentation key.
|
|
669
|
+
* Provides the presentation key.
|
|
618
670
|
*/
|
|
619
671
|
async providePresentationKey(key: number[]): Promise<void> {
|
|
620
672
|
if (this.authenticated) {
|
|
@@ -640,17 +692,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
640
692
|
}
|
|
641
693
|
|
|
642
694
|
/**
|
|
643
|
-
* Provides the password
|
|
644
|
-
*
|
|
645
|
-
* - **Existing user**:
|
|
646
|
-
* Decrypts the primary key using the provided password (and either the presentation key or recovery key, depending on the mode).
|
|
647
|
-
* Then builds the underlying wallet, marking the user as authenticated.
|
|
648
|
-
*
|
|
649
|
-
* - **New user**:
|
|
650
|
-
* Generates a new UMP token with fresh keys (primary, privileged, recovery). Publishes it on-chain and builds the wallet.
|
|
651
|
-
*
|
|
652
|
-
* @param password The user's password as a string.
|
|
653
|
-
* @throws {Error} If the user is already authenticated, if the mode does not use a password, or if required keys are missing.
|
|
695
|
+
* Provides the password.
|
|
654
696
|
*/
|
|
655
697
|
async providePassword(password: string): Promise<void> {
|
|
656
698
|
if (this.authenticated) {
|
|
@@ -660,12 +702,10 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
660
702
|
throw new Error('Password is not needed in this mode')
|
|
661
703
|
}
|
|
662
704
|
|
|
663
|
-
// If we detect an existing user flow:
|
|
664
705
|
if (this.authenticationFlow === 'existing-user') {
|
|
706
|
+
// Existing user flow
|
|
665
707
|
if (!this.currentUMPToken) {
|
|
666
|
-
throw new Error(
|
|
667
|
-
'Provide either a presentation key or a recovery key first, depending on the authentication mode.'
|
|
668
|
-
)
|
|
708
|
+
throw new Error('Provide presentation or recovery key first.')
|
|
669
709
|
}
|
|
670
710
|
const derivedPasswordKey = Hash.pbkdf2(
|
|
671
711
|
Utils.toArray(password, 'utf8'),
|
|
@@ -675,140 +715,123 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
675
715
|
'sha512'
|
|
676
716
|
)
|
|
677
717
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
throw new Error('No presentation key found!')
|
|
681
|
-
}
|
|
718
|
+
let rootPrimaryKey: number[]
|
|
719
|
+
let rootPrivilegedKey: number[] | undefined // Only needed for recovery mode
|
|
682
720
|
|
|
683
|
-
|
|
721
|
+
if (this.authenticationMode === 'presentation-key-and-password') {
|
|
722
|
+
if (!this.presentationKey) throw new Error('No presentation key found!')
|
|
684
723
|
const xorKey = this.XOR(this.presentationKey, derivedPasswordKey)
|
|
685
|
-
|
|
686
|
-
this.currentUMPToken.passwordPresentationPrimary
|
|
687
|
-
) as number[]
|
|
688
|
-
|
|
689
|
-
await this.buildUnderlying(decryptedPrimary)
|
|
724
|
+
rootPrimaryKey = new SymmetricKey(xorKey).decrypt(this.currentUMPToken.passwordPresentationPrimary) as number[]
|
|
690
725
|
} else {
|
|
691
|
-
// 'recovery-key-and-password'
|
|
692
|
-
if (!this.recoveryKey)
|
|
693
|
-
throw new Error('No recovery key found!')
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// Decrypt the primary key with XOR(recoveryKey, derivedPasswordKey).
|
|
726
|
+
// 'recovery-key-and-password'
|
|
727
|
+
if (!this.recoveryKey) throw new Error('No recovery key found!')
|
|
697
728
|
const primaryDecryptionKey = this.XOR(this.recoveryKey, derivedPasswordKey)
|
|
698
|
-
|
|
729
|
+
rootPrimaryKey = new SymmetricKey(primaryDecryptionKey).decrypt(
|
|
699
730
|
this.currentUMPToken.passwordRecoveryPrimary
|
|
700
731
|
) as number[]
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const privilegedDecryptionKey = this.XOR(decryptedPrimary, derivedPasswordKey)
|
|
704
|
-
const decryptedPrivileged = new SymmetricKey(privilegedDecryptionKey).decrypt(
|
|
732
|
+
const privilegedDecryptionKey = this.XOR(rootPrimaryKey, derivedPasswordKey)
|
|
733
|
+
rootPrivilegedKey = new SymmetricKey(privilegedDecryptionKey).decrypt(
|
|
705
734
|
this.currentUMPToken.passwordPrimaryPrivileged
|
|
706
735
|
) as number[]
|
|
707
|
-
|
|
708
|
-
|
|
736
|
+
}
|
|
737
|
+
// Build root infrastructure, load profiles, and switch to default profile initially
|
|
738
|
+
await this.setupRootInfrastructure(rootPrimaryKey, rootPrivilegedKey)
|
|
739
|
+
await this.switchProfile(this.activeProfileId)
|
|
740
|
+
} else {
|
|
741
|
+
// New user flow (only 'presentation-key-and-password')
|
|
742
|
+
if (this.authenticationMode !== 'presentation-key-and-password') {
|
|
743
|
+
throw new Error('New-user flow requires presentation key and password mode.')
|
|
744
|
+
}
|
|
745
|
+
if (!this.presentationKey) {
|
|
746
|
+
throw new Error('No presentation key provided for new-user flow.')
|
|
709
747
|
}
|
|
710
748
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
749
|
+
// Generate new keys/salt
|
|
750
|
+
const recoveryKey = Random(32)
|
|
751
|
+
await this.recoveryKeySaver(recoveryKey)
|
|
752
|
+
const passwordSalt = Random(32)
|
|
753
|
+
const passwordKey = Hash.pbkdf2(Utils.toArray(password, 'utf8'), passwordSalt, PBKDF2_NUM_ROUNDS, 32, 'sha512')
|
|
754
|
+
const rootPrimaryKey = Random(32)
|
|
755
|
+
const rootPrivilegedKey = Random(32)
|
|
756
|
+
|
|
757
|
+
// Build XOR keys
|
|
758
|
+
const presentationPassword = new SymmetricKey(this.XOR(this.presentationKey, passwordKey))
|
|
759
|
+
const presentationRecovery = new SymmetricKey(this.XOR(this.presentationKey, recoveryKey))
|
|
760
|
+
const recoveryPassword = new SymmetricKey(this.XOR(recoveryKey, passwordKey))
|
|
761
|
+
const primaryPassword = new SymmetricKey(this.XOR(rootPrimaryKey, passwordKey))
|
|
762
|
+
|
|
763
|
+
// Temp manager for encryption
|
|
764
|
+
const tempPrivilegedKeyManager = new PrivilegedKeyManager(async () => new PrivateKey(rootPrivilegedKey))
|
|
765
|
+
|
|
766
|
+
// Build new UMP token (no profiles initially)
|
|
767
|
+
const newToken: UMPToken = {
|
|
768
|
+
passwordSalt,
|
|
769
|
+
passwordPresentationPrimary: presentationPassword.encrypt(rootPrimaryKey) as number[],
|
|
770
|
+
passwordRecoveryPrimary: recoveryPassword.encrypt(rootPrimaryKey) as number[],
|
|
771
|
+
presentationRecoveryPrimary: presentationRecovery.encrypt(rootPrimaryKey) as number[],
|
|
772
|
+
passwordPrimaryPrivileged: primaryPassword.encrypt(rootPrivilegedKey) as number[],
|
|
773
|
+
presentationRecoveryPrivileged: presentationRecovery.encrypt(rootPrivilegedKey) as number[],
|
|
774
|
+
presentationHash: Hash.sha256(this.presentationKey),
|
|
775
|
+
recoveryHash: Hash.sha256(recoveryKey),
|
|
776
|
+
presentationKeyEncrypted: (
|
|
777
|
+
await tempPrivilegedKeyManager.encrypt({
|
|
778
|
+
plaintext: this.presentationKey,
|
|
779
|
+
protocolID: [2, 'admin key wrapping'],
|
|
780
|
+
keyID: '1'
|
|
781
|
+
})
|
|
782
|
+
).ciphertext,
|
|
783
|
+
passwordKeyEncrypted: (
|
|
784
|
+
await tempPrivilegedKeyManager.encrypt({
|
|
785
|
+
plaintext: passwordKey,
|
|
786
|
+
protocolID: [2, 'admin key wrapping'],
|
|
787
|
+
keyID: '1'
|
|
788
|
+
})
|
|
789
|
+
).ciphertext,
|
|
790
|
+
recoveryKeyEncrypted: (
|
|
791
|
+
await tempPrivilegedKeyManager.encrypt({
|
|
792
|
+
plaintext: recoveryKey,
|
|
793
|
+
protocolID: [2, 'admin key wrapping'],
|
|
794
|
+
keyID: '1'
|
|
795
|
+
})
|
|
796
|
+
).ciphertext,
|
|
797
|
+
profilesEncrypted: undefined // No profiles yet
|
|
798
|
+
}
|
|
799
|
+
this.currentUMPToken = newToken
|
|
738
800
|
|
|
739
|
-
|
|
740
|
-
|
|
801
|
+
// Setup root infrastructure and switch to default profile
|
|
802
|
+
await this.setupRootInfrastructure(rootPrimaryKey)
|
|
803
|
+
await this.switchProfile(DEFAULT_PROFILE_ID)
|
|
741
804
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
recoveryHash: Hash.sha256(recoveryKey),
|
|
752
|
-
presentationKeyEncrypted: (
|
|
753
|
-
await tempPrivilegedKeyManager.encrypt({
|
|
754
|
-
plaintext: this.presentationKey,
|
|
755
|
-
protocolID: [2, 'admin key wrapping'],
|
|
756
|
-
keyID: '1'
|
|
757
|
-
})
|
|
758
|
-
).ciphertext,
|
|
759
|
-
passwordKeyEncrypted: (
|
|
760
|
-
await tempPrivilegedKeyManager.encrypt({
|
|
761
|
-
plaintext: passwordKey,
|
|
762
|
-
protocolID: [2, 'admin key wrapping'],
|
|
763
|
-
keyID: '1'
|
|
764
|
-
})
|
|
765
|
-
).ciphertext,
|
|
766
|
-
recoveryKeyEncrypted: (
|
|
767
|
-
await tempPrivilegedKeyManager.encrypt({
|
|
768
|
-
plaintext: recoveryKey,
|
|
769
|
-
protocolID: [2, 'admin key wrapping'],
|
|
770
|
-
keyID: '1'
|
|
771
|
-
})
|
|
772
|
-
).ciphertext
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// Now, we can create our new wallet!
|
|
776
|
-
this.currentUMPToken = newToken
|
|
777
|
-
await this.buildUnderlying(primaryKey)
|
|
805
|
+
// Fund the *default* wallet if funder provided
|
|
806
|
+
if (this.newWalletFunder && this.underlying) {
|
|
807
|
+
try {
|
|
808
|
+
await this.newWalletFunder(this.presentationKey, this.underlying, this.adminOriginator)
|
|
809
|
+
} catch (e) {
|
|
810
|
+
console.error('Error funding new wallet:', e)
|
|
811
|
+
// Decide if this should halt the process or just log
|
|
812
|
+
}
|
|
813
|
+
}
|
|
778
814
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
await this.newWalletFunder(this.presentationKey, this.underlying!, this.adminOriginator)
|
|
784
|
-
} catch (e) {
|
|
785
|
-
// swallow error
|
|
786
|
-
// TODO: Implement better error handling
|
|
787
|
-
console.error(e)
|
|
815
|
+
// Publish the new UMP token *after* potentially funding
|
|
816
|
+
// We need the default profile wallet to sign the UMP creation TX
|
|
817
|
+
if (!this.underlying) {
|
|
818
|
+
throw new Error('Default profile wallet not built before attempting to publish UMP token.')
|
|
788
819
|
}
|
|
820
|
+
this.currentUMPToken.currentOutpoint = await this.UMPTokenInteractor.buildAndSend(
|
|
821
|
+
this.underlying, // Use the default profile wallet
|
|
822
|
+
this.adminOriginator,
|
|
823
|
+
newToken
|
|
824
|
+
)
|
|
789
825
|
}
|
|
790
|
-
|
|
791
|
-
// Publish the new UMP token on-chain and store the resulting outpoint.
|
|
792
|
-
this.currentUMPToken.currentOutpoint = await this.UMPTokenInteractor.buildAndSend(
|
|
793
|
-
this.underlying!,
|
|
794
|
-
this.adminOriginator,
|
|
795
|
-
newToken
|
|
796
|
-
)
|
|
797
826
|
}
|
|
798
827
|
|
|
799
828
|
/**
|
|
800
|
-
* Provides the recovery key
|
|
801
|
-
*
|
|
802
|
-
* @param recoveryKey The user's recovery key (32 bytes).
|
|
803
|
-
* @throws {Error} if user is already authenticated, if the mode does not use a recovery key,
|
|
804
|
-
* or if a required presentation key is missing in "presentation-key-and-recovery-key" mode.
|
|
829
|
+
* Provides the recovery key.
|
|
805
830
|
*/
|
|
806
831
|
async provideRecoveryKey(recoveryKey: number[]): Promise<void> {
|
|
807
832
|
if (this.authenticated) {
|
|
808
833
|
throw new Error('Already authenticated')
|
|
809
834
|
}
|
|
810
|
-
|
|
811
|
-
// Cannot use recovery key in a new-user flow
|
|
812
835
|
if (this.authenticationFlow === 'new-user') {
|
|
813
836
|
throw new Error('Do not submit recovery key in new-user flow')
|
|
814
837
|
}
|
|
@@ -816,390 +839,605 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
816
839
|
if (this.authenticationMode === 'presentation-key-and-password') {
|
|
817
840
|
throw new Error('No recovery key required in this mode')
|
|
818
841
|
} else if (this.authenticationMode === 'recovery-key-and-password') {
|
|
819
|
-
//
|
|
842
|
+
// Wait for password
|
|
820
843
|
const hash = Hash.sha256(recoveryKey)
|
|
821
844
|
const token = await this.UMPTokenInteractor.findByRecoveryKeyHash(hash)
|
|
822
|
-
if (!token)
|
|
823
|
-
throw new Error('No user found with this key')
|
|
824
|
-
}
|
|
845
|
+
if (!token) throw new Error('No user found with this recovery key')
|
|
825
846
|
this.recoveryKey = recoveryKey
|
|
826
847
|
this.currentUMPToken = token
|
|
827
848
|
} else {
|
|
828
849
|
// 'presentation-key-and-recovery-key'
|
|
829
|
-
if (!this.presentationKey)
|
|
830
|
-
|
|
831
|
-
}
|
|
832
|
-
if (!this.currentUMPToken) {
|
|
833
|
-
throw new Error('Current UMP token not found')
|
|
834
|
-
}
|
|
850
|
+
if (!this.presentationKey) throw new Error('Provide the presentation key first')
|
|
851
|
+
if (!this.currentUMPToken) throw new Error('Current UMP token not found')
|
|
835
852
|
|
|
836
|
-
// Decrypt the primary key:
|
|
837
853
|
const xorKey = this.XOR(this.presentationKey, recoveryKey)
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const
|
|
854
|
+
const rootPrimaryKey = new SymmetricKey(xorKey).decrypt(
|
|
855
|
+
this.currentUMPToken.presentationRecoveryPrimary
|
|
856
|
+
) as number[]
|
|
857
|
+
const rootPrivilegedKey = new SymmetricKey(xorKey).decrypt(
|
|
842
858
|
this.currentUMPToken.presentationRecoveryPrivileged
|
|
843
859
|
) as number[]
|
|
844
860
|
|
|
845
|
-
|
|
861
|
+
// Build root infrastructure, load profiles, switch to default
|
|
862
|
+
await this.setupRootInfrastructure(rootPrimaryKey, rootPrivilegedKey)
|
|
863
|
+
await this.switchProfile(this.activeProfileId)
|
|
846
864
|
}
|
|
847
865
|
}
|
|
848
866
|
|
|
867
|
+
// --- State Management Methods ---
|
|
868
|
+
|
|
849
869
|
/**
|
|
850
|
-
* Saves the current wallet state (
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
* @remarks
|
|
855
|
-
* Storing the snapshot provides a fully authenticated state.
|
|
856
|
-
* This **must** be securely stored (e.g. system keychain or encrypted file).
|
|
857
|
-
* If attackers gain access to this snapshot, they can fully control the wallet.
|
|
870
|
+
* Saves the current wallet state (root key, UMP token, active profile) into an encrypted snapshot.
|
|
871
|
+
* Version 2 format: [1 byte version=2] + [32 byte snapshot key] + [16 byte activeProfileId] + [encrypted payload]
|
|
872
|
+
* Encrypted Payload: [32 byte rootPrimaryKey] + [varint token length + serialized UMP token]
|
|
858
873
|
*
|
|
859
|
-
* @returns
|
|
860
|
-
* @throws {Error} if no primary key or token is currently set.
|
|
874
|
+
* @returns Encrypted snapshot bytes.
|
|
861
875
|
*/
|
|
862
876
|
saveSnapshot(): number[] {
|
|
863
|
-
if (!this.
|
|
864
|
-
throw new Error('No primary key or current UMP token set')
|
|
877
|
+
if (!this.rootPrimaryKey || !this.currentUMPToken) {
|
|
878
|
+
throw new Error('No root primary key or current UMP token set')
|
|
865
879
|
}
|
|
866
880
|
|
|
867
|
-
// Generate a random snapshot encryption key:
|
|
868
881
|
const snapshotKey = Random(32)
|
|
869
|
-
|
|
870
|
-
// Serialize the relevant data to a preimage buffer:
|
|
871
882
|
const snapshotPreimageWriter = new Utils.Writer()
|
|
872
883
|
|
|
873
|
-
// Write
|
|
874
|
-
snapshotPreimageWriter.write(this.
|
|
884
|
+
// Write root primary key
|
|
885
|
+
snapshotPreimageWriter.write(this.rootPrimaryKey)
|
|
875
886
|
|
|
876
|
-
// Write
|
|
887
|
+
// Write serialized UMP token (must have outpoint)
|
|
888
|
+
if (!this.currentUMPToken.currentOutpoint) {
|
|
889
|
+
throw new Error('UMP token cannot be saved without a current outpoint.')
|
|
890
|
+
}
|
|
877
891
|
const serializedToken = this.serializeUMPToken(this.currentUMPToken)
|
|
892
|
+
snapshotPreimageWriter.writeVarIntNum(serializedToken.length)
|
|
878
893
|
snapshotPreimageWriter.write(serializedToken)
|
|
879
894
|
|
|
880
|
-
// Encrypt the
|
|
895
|
+
// Encrypt the payload
|
|
881
896
|
const snapshotPreimage = snapshotPreimageWriter.toArray()
|
|
882
897
|
const snapshotPayload = new SymmetricKey(snapshotKey).encrypt(snapshotPreimage) as number[]
|
|
883
898
|
|
|
884
|
-
// Build
|
|
899
|
+
// Build final snapshot (Version 2)
|
|
885
900
|
const snapshotWriter = new Utils.Writer()
|
|
901
|
+
snapshotWriter.writeUInt8(2) // Version
|
|
886
902
|
snapshotWriter.write(snapshotKey)
|
|
887
|
-
snapshotWriter.write(
|
|
903
|
+
snapshotWriter.write(this.activeProfileId) // Active profile ID
|
|
904
|
+
snapshotWriter.write(snapshotPayload) // Encrypted data
|
|
888
905
|
|
|
889
906
|
return snapshotWriter.toArray()
|
|
890
907
|
}
|
|
891
908
|
|
|
892
909
|
/**
|
|
893
|
-
* Loads a previously saved state snapshot
|
|
894
|
-
*
|
|
910
|
+
* Loads a previously saved state snapshot. Restores root key, UMP token, profiles, and active profile.
|
|
911
|
+
* Handles Version 1 (legacy) and Version 2 formats.
|
|
895
912
|
*
|
|
896
|
-
* @param snapshot
|
|
897
|
-
* @throws {Error} If the snapshot format is invalid or decryption fails.
|
|
913
|
+
* @param snapshot Encrypted snapshot bytes.
|
|
898
914
|
*/
|
|
899
915
|
async loadSnapshot(snapshot: number[]): Promise<void> {
|
|
900
916
|
try {
|
|
901
917
|
const reader = new Utils.Reader(snapshot)
|
|
918
|
+
const version = reader.readUInt8()
|
|
919
|
+
|
|
920
|
+
let snapshotKey: number[]
|
|
921
|
+
let encryptedPayload: number[]
|
|
922
|
+
let activeProfileId = DEFAULT_PROFILE_ID // Default for V1
|
|
923
|
+
|
|
924
|
+
if (version === 1) {
|
|
925
|
+
snapshotKey = reader.read(32)
|
|
926
|
+
encryptedPayload = reader.read()
|
|
927
|
+
} else if (version === 2) {
|
|
928
|
+
snapshotKey = reader.read(32)
|
|
929
|
+
activeProfileId = reader.read(16) // Read active profile ID
|
|
930
|
+
encryptedPayload = reader.read()
|
|
931
|
+
} else {
|
|
932
|
+
throw new Error(`Unsupported snapshot version: ${version}`)
|
|
933
|
+
}
|
|
902
934
|
|
|
903
|
-
//
|
|
904
|
-
const snapshotKey = reader.read(32)
|
|
905
|
-
|
|
906
|
-
// The rest is the encrypted payload:
|
|
907
|
-
const encryptedPayload = reader.read()
|
|
908
|
-
|
|
909
|
-
// Decrypt the payload:
|
|
935
|
+
// Decrypt payload
|
|
910
936
|
const decryptedPayload = new SymmetricKey(snapshotKey).decrypt(encryptedPayload) as number[]
|
|
911
|
-
|
|
912
937
|
const payloadReader = new Utils.Reader(decryptedPayload)
|
|
913
938
|
|
|
914
|
-
// Read
|
|
915
|
-
const
|
|
939
|
+
// Read root primary key
|
|
940
|
+
const rootPrimaryKey = payloadReader.read(32)
|
|
916
941
|
|
|
917
|
-
// Read
|
|
918
|
-
const
|
|
942
|
+
// Read serialized UMP token
|
|
943
|
+
const tokenLen = payloadReader.readVarIntNum()
|
|
944
|
+
const tokenBytes = payloadReader.read(tokenLen)
|
|
919
945
|
const token = this.deserializeUMPToken(tokenBytes)
|
|
920
946
|
|
|
921
|
-
// Assign
|
|
947
|
+
// Assign loaded data
|
|
922
948
|
this.currentUMPToken = token
|
|
923
|
-
|
|
949
|
+
|
|
950
|
+
// Setup root infrastructure, load profiles, and switch to the loaded active profile
|
|
951
|
+
await this.setupRootInfrastructure(rootPrimaryKey) // Will automatically load profiles
|
|
952
|
+
await this.switchProfile(activeProfileId) // Switch to the profile saved in the snapshot
|
|
953
|
+
|
|
954
|
+
this.authenticationFlow = 'existing-user' // Loading implies existing user
|
|
924
955
|
} catch (error) {
|
|
956
|
+
this.destroy() // Clear state on error
|
|
925
957
|
throw new Error(`Failed to load snapshot: ${(error as Error).message}`)
|
|
926
958
|
}
|
|
927
959
|
}
|
|
928
960
|
|
|
929
961
|
/**
|
|
930
|
-
* Destroys the
|
|
962
|
+
* Destroys the wallet state, clearing keys, tokens, and profiles.
|
|
931
963
|
*/
|
|
932
964
|
destroy(): void {
|
|
933
965
|
this.underlying = undefined
|
|
934
|
-
this.
|
|
966
|
+
this.rootPrivilegedKeyManager = undefined
|
|
935
967
|
this.authenticated = false
|
|
936
|
-
this.
|
|
968
|
+
this.rootPrimaryKey = undefined
|
|
937
969
|
this.currentUMPToken = undefined
|
|
938
970
|
this.presentationKey = undefined
|
|
939
971
|
this.recoveryKey = undefined
|
|
972
|
+
this.profiles = []
|
|
973
|
+
this.activeProfileId = DEFAULT_PROFILE_ID
|
|
940
974
|
this.authenticationMode = 'presentation-key-and-password'
|
|
941
975
|
this.authenticationFlow = 'new-user'
|
|
942
976
|
}
|
|
943
977
|
|
|
978
|
+
// --- Profile Management Methods ---
|
|
979
|
+
|
|
944
980
|
/**
|
|
945
|
-
*
|
|
946
|
-
*
|
|
947
|
-
* @param newPassword The user's new password as a string.
|
|
948
|
-
* @throws {Error} If the user is not authenticated, or if underlying token references are missing.
|
|
981
|
+
* Lists all available profiles, including the default profile.
|
|
982
|
+
* @returns Array of profile info objects, including an 'active' flag.
|
|
949
983
|
*/
|
|
950
|
-
|
|
984
|
+
listProfiles(): Array<{ id: number[]; name: string; createdAt: number | null; active: boolean }> {
|
|
951
985
|
if (!this.authenticated) {
|
|
952
986
|
throw new Error('Not authenticated.')
|
|
953
987
|
}
|
|
954
|
-
|
|
955
|
-
|
|
988
|
+
const profileList = [
|
|
989
|
+
// Default profile
|
|
990
|
+
{
|
|
991
|
+
id: DEFAULT_PROFILE_ID,
|
|
992
|
+
name: 'default',
|
|
993
|
+
createdAt: null, // Default profile doesn't have a creation timestamp in the same way
|
|
994
|
+
active: this.activeProfileId.every(x => x === 0)
|
|
995
|
+
},
|
|
996
|
+
// Other profiles
|
|
997
|
+
...this.profiles.map(p => ({
|
|
998
|
+
id: p.id,
|
|
999
|
+
name: p.name,
|
|
1000
|
+
createdAt: p.createdAt,
|
|
1001
|
+
active: this.activeProfileId.every((x, i) => x === p.id[i])
|
|
1002
|
+
}))
|
|
1003
|
+
]
|
|
1004
|
+
return profileList
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Adds a new profile with the given name.
|
|
1009
|
+
* Generates necessary pads and updates the UMP token.
|
|
1010
|
+
* Does not switch to the new profile automatically.
|
|
1011
|
+
*
|
|
1012
|
+
* @param name The desired name for the new profile.
|
|
1013
|
+
* @returns The ID of the newly created profile.
|
|
1014
|
+
*/
|
|
1015
|
+
async addProfile(name: string): Promise<number[]> {
|
|
1016
|
+
if (!this.authenticated || !this.rootPrimaryKey || !this.currentUMPToken || !this.rootPrivilegedKeyManager) {
|
|
1017
|
+
throw new Error('Wallet not fully initialized or authenticated.')
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Ensure name is unique (including 'default')
|
|
1021
|
+
if (name === 'default' || this.profiles.some(p => p.name.toLowerCase() === name.toLowerCase())) {
|
|
1022
|
+
throw new Error(`Profile name "${name}" is already in use.`)
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const newProfile: Profile = {
|
|
1026
|
+
name,
|
|
1027
|
+
id: Random(16),
|
|
1028
|
+
primaryPad: Random(32),
|
|
1029
|
+
privilegedPad: Random(32),
|
|
1030
|
+
createdAt: Math.floor(Date.now() / 1000)
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
this.profiles.push(newProfile)
|
|
1034
|
+
|
|
1035
|
+
// Update the UMP token with the new profile list
|
|
1036
|
+
await this.updateAuthFactors(
|
|
1037
|
+
this.currentUMPToken.passwordSalt,
|
|
1038
|
+
// Need to re-derive/decrypt factors needed for re-encryption
|
|
1039
|
+
await this.getFactor('passwordKey'),
|
|
1040
|
+
await this.getFactor('presentationKey'),
|
|
1041
|
+
await this.getFactor('recoveryKey'),
|
|
1042
|
+
this.rootPrimaryKey,
|
|
1043
|
+
await this.getFactor('privilegedKey', true), // Get ROOT privileged key
|
|
1044
|
+
this.profiles // Pass the updated profile list
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
return newProfile.id
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Deletes a profile by its ID.
|
|
1052
|
+
* Cannot delete the default profile. If the active profile is deleted,
|
|
1053
|
+
* it switches back to the default profile.
|
|
1054
|
+
*
|
|
1055
|
+
* @param profileId The 16-byte ID of the profile to delete.
|
|
1056
|
+
*/
|
|
1057
|
+
async deleteProfile(profileId: number[]): Promise<void> {
|
|
1058
|
+
if (!this.authenticated || !this.rootPrimaryKey || !this.currentUMPToken || !this.rootPrivilegedKeyManager) {
|
|
1059
|
+
throw new Error('Wallet not fully initialized or authenticated.')
|
|
1060
|
+
}
|
|
1061
|
+
if (profileId.every(x => x === 0)) {
|
|
1062
|
+
throw new Error('Cannot delete the default profile.')
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const profileIndex = this.profiles.findIndex(p => p.id.every((x, i) => x === profileId[i]))
|
|
1066
|
+
if (profileIndex === -1) {
|
|
1067
|
+
throw new Error('Profile not found.')
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Remove the profile
|
|
1071
|
+
this.profiles.splice(profileIndex, 1)
|
|
1072
|
+
|
|
1073
|
+
// If the deleted profile was active, switch to default
|
|
1074
|
+
if (this.activeProfileId.every((x, i) => x === profileId[i])) {
|
|
1075
|
+
await this.switchProfile(DEFAULT_PROFILE_ID) // This rebuilds the wallet
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Update the UMP token
|
|
1079
|
+
await this.updateAuthFactors(
|
|
1080
|
+
this.currentUMPToken.passwordSalt,
|
|
1081
|
+
await this.getFactor('passwordKey'),
|
|
1082
|
+
await this.getFactor('presentationKey'),
|
|
1083
|
+
await this.getFactor('recoveryKey'),
|
|
1084
|
+
this.rootPrimaryKey,
|
|
1085
|
+
await this.getFactor('privilegedKey', true), // Get ROOT privileged key
|
|
1086
|
+
this.profiles // Pass updated list
|
|
1087
|
+
)
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Switches the active profile. This re-derives keys and rebuilds the underlying wallet.
|
|
1092
|
+
*
|
|
1093
|
+
* @param profileId The 16-byte ID of the profile to switch to (use DEFAULT_PROFILE_ID for default).
|
|
1094
|
+
*/
|
|
1095
|
+
async switchProfile(profileId: number[]): Promise<void> {
|
|
1096
|
+
if (!this.authenticated || !this.rootPrimaryKey || !this.rootPrivilegedKeyManager) {
|
|
1097
|
+
throw new Error('Cannot switch profile: Wallet not authenticated or root keys missing.')
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
let profilePrimaryKey: number[]
|
|
1101
|
+
let profilePrivilegedPad: number[] | undefined // Pad for the target profile
|
|
1102
|
+
|
|
1103
|
+
if (profileId.every(x => x === 0)) {
|
|
1104
|
+
// Switching to default profile
|
|
1105
|
+
profilePrimaryKey = this.rootPrimaryKey
|
|
1106
|
+
profilePrivilegedPad = undefined // No pad for default
|
|
1107
|
+
this.activeProfileId = DEFAULT_PROFILE_ID
|
|
1108
|
+
} else {
|
|
1109
|
+
// Switching to a non-default profile
|
|
1110
|
+
const profile = this.profiles.find(p => p.id.every((x, i) => x === profileId[i]))
|
|
1111
|
+
if (!profile) {
|
|
1112
|
+
throw new Error('Profile not found.')
|
|
1113
|
+
}
|
|
1114
|
+
profilePrimaryKey = this.XOR(this.rootPrimaryKey, profile.primaryPad)
|
|
1115
|
+
profilePrivilegedPad = profile.privilegedPad
|
|
1116
|
+
this.activeProfileId = profileId
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// Create a *profile-specific* PrivilegedKeyManager.
|
|
1120
|
+
// It uses the ROOT manager internally but applies the profile's pad.
|
|
1121
|
+
const profilePrivilegedKeyManager = new PrivilegedKeyManager(async (reason: string) => {
|
|
1122
|
+
// Request the ROOT privileged key using the root manager
|
|
1123
|
+
const rootPrivileged: PrivateKey = await (this.rootPrivilegedKeyManager as any).getPrivilegedKey(reason)
|
|
1124
|
+
const rootPrivilegedBytes = rootPrivileged.toArray()
|
|
1125
|
+
|
|
1126
|
+
// Apply the profile's pad if applicable
|
|
1127
|
+
const profilePrivilegedBytes = profilePrivilegedPad
|
|
1128
|
+
? this.XOR(rootPrivilegedBytes, profilePrivilegedPad)
|
|
1129
|
+
: rootPrivilegedBytes
|
|
1130
|
+
|
|
1131
|
+
return new PrivateKey(profilePrivilegedBytes)
|
|
1132
|
+
})
|
|
1133
|
+
|
|
1134
|
+
// Build the underlying wallet for the specific profile
|
|
1135
|
+
this.underlying = await this.walletBuilder(
|
|
1136
|
+
profilePrimaryKey,
|
|
1137
|
+
profilePrivilegedKeyManager, // Pass the profile-specific manager
|
|
1138
|
+
this.activeProfileId // Pass the ID of the profile being activated
|
|
1139
|
+
)
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// --- Key Management Methods ---
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Changes the user's password. Re-wraps keys and updates the UMP token.
|
|
1146
|
+
*/
|
|
1147
|
+
async changePassword(newPassword: string): Promise<void> {
|
|
1148
|
+
if (!this.authenticated || !this.currentUMPToken || !this.rootPrimaryKey || !this.rootPrivilegedKeyManager) {
|
|
1149
|
+
throw new Error('Not authenticated or missing required data.')
|
|
956
1150
|
}
|
|
957
1151
|
|
|
958
1152
|
const passwordSalt = Random(32)
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
)
|
|
969
|
-
const presentationKey = (
|
|
970
|
-
|
|
971
|
-
ciphertext: this.currentUMPToken.presentationKeyEncrypted,
|
|
972
|
-
protocolID: [2, 'admin key wrapping'],
|
|
973
|
-
keyID: '1'
|
|
974
|
-
})
|
|
975
|
-
).plaintext
|
|
976
|
-
const privilegedKey = new SymmetricKey(this.XOR(presentationKey, recoveryKey)).decrypt(
|
|
977
|
-
this.currentUMPToken.presentationRecoveryPrivileged
|
|
978
|
-
) as number[]
|
|
1153
|
+
const newPasswordKey = Hash.pbkdf2(
|
|
1154
|
+
Utils.toArray(newPassword, 'utf8'),
|
|
1155
|
+
passwordSalt,
|
|
1156
|
+
PBKDF2_NUM_ROUNDS,
|
|
1157
|
+
32,
|
|
1158
|
+
'sha512'
|
|
1159
|
+
)
|
|
1160
|
+
|
|
1161
|
+
// Decrypt existing factors needed for re-encryption, using the *root* privileged key manager
|
|
1162
|
+
const recoveryKey = await this.getFactor('recoveryKey')
|
|
1163
|
+
const presentationKey = await this.getFactor('presentationKey')
|
|
1164
|
+
const rootPrivilegedKey = await this.getFactor('privilegedKey', true) // Get ROOT privileged key
|
|
979
1165
|
|
|
980
1166
|
await this.updateAuthFactors(
|
|
981
1167
|
passwordSalt,
|
|
982
|
-
|
|
1168
|
+
newPasswordKey,
|
|
983
1169
|
presentationKey,
|
|
984
1170
|
recoveryKey,
|
|
985
|
-
this.
|
|
986
|
-
|
|
1171
|
+
this.rootPrimaryKey,
|
|
1172
|
+
rootPrivilegedKey, // Pass the explicitly fetched root key
|
|
1173
|
+
this.profiles // Preserve existing profiles
|
|
987
1174
|
)
|
|
988
1175
|
}
|
|
989
1176
|
|
|
990
1177
|
/**
|
|
991
|
-
* Retrieves the current recovery key.
|
|
992
|
-
*
|
|
993
|
-
* @throws {Error} If the user is not authenticated, or if underlying token references are missing.
|
|
1178
|
+
* Retrieves the current recovery key. Requires privileged access.
|
|
994
1179
|
*/
|
|
995
1180
|
async getRecoveryKey(): Promise<number[]> {
|
|
996
|
-
if (!this.authenticated) {
|
|
997
|
-
throw new Error('Not authenticated.')
|
|
1181
|
+
if (!this.authenticated || !this.currentUMPToken || !this.rootPrivilegedKeyManager) {
|
|
1182
|
+
throw new Error('Not authenticated or missing required data.')
|
|
998
1183
|
}
|
|
999
|
-
|
|
1000
|
-
throw new Error('No UMP token!')
|
|
1001
|
-
}
|
|
1002
|
-
return (
|
|
1003
|
-
await this.underlyingPrivilegedKeyManager!.decrypt({
|
|
1004
|
-
ciphertext: this.currentUMPToken.recoveryKeyEncrypted,
|
|
1005
|
-
protocolID: [2, 'admin key wrapping'],
|
|
1006
|
-
keyID: '1'
|
|
1007
|
-
})
|
|
1008
|
-
).plaintext
|
|
1184
|
+
return this.getFactor('recoveryKey')
|
|
1009
1185
|
}
|
|
1010
1186
|
|
|
1011
1187
|
/**
|
|
1012
|
-
* Changes the user's recovery key
|
|
1013
|
-
*
|
|
1014
|
-
* @throws {Error} If the user is not authenticated, or if underlying token references are missing.
|
|
1188
|
+
* Changes the user's recovery key. Prompts user to save the new key.
|
|
1015
1189
|
*/
|
|
1016
1190
|
async changeRecoveryKey(): Promise<void> {
|
|
1017
|
-
if (!this.authenticated) {
|
|
1018
|
-
throw new Error('Not authenticated.')
|
|
1019
|
-
}
|
|
1020
|
-
if (!this.currentUMPToken) {
|
|
1021
|
-
throw new Error('No UMP token to update.')
|
|
1191
|
+
if (!this.authenticated || !this.currentUMPToken || !this.rootPrimaryKey || !this.rootPrivilegedKeyManager) {
|
|
1192
|
+
throw new Error('Not authenticated or missing required data.')
|
|
1022
1193
|
}
|
|
1023
1194
|
|
|
1024
|
-
// Decrypt existing
|
|
1025
|
-
const passwordKey = (
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
protocolID: [2, 'admin key wrapping'],
|
|
1029
|
-
keyID: '1'
|
|
1030
|
-
})
|
|
1031
|
-
).plaintext
|
|
1032
|
-
const presentationKey = (
|
|
1033
|
-
await this.underlyingPrivilegedKeyManager!.decrypt({
|
|
1034
|
-
ciphertext: this.currentUMPToken.presentationKeyEncrypted,
|
|
1035
|
-
protocolID: [2, 'admin key wrapping'],
|
|
1036
|
-
keyID: '1'
|
|
1037
|
-
})
|
|
1038
|
-
).plaintext
|
|
1039
|
-
const privilegedKey = new SymmetricKey(this.XOR(passwordKey, this.primaryKey!)).decrypt(
|
|
1040
|
-
this.currentUMPToken.passwordPrimaryPrivileged
|
|
1041
|
-
) as number[]
|
|
1195
|
+
// Decrypt existing factors needed
|
|
1196
|
+
const passwordKey = await this.getFactor('passwordKey')
|
|
1197
|
+
const presentationKey = await this.getFactor('presentationKey')
|
|
1198
|
+
const rootPrivilegedKey = await this.getFactor('privilegedKey', true) // Get ROOT privileged key
|
|
1042
1199
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1200
|
+
// Generate and save new recovery key
|
|
1201
|
+
const newRecoveryKey = Random(32)
|
|
1202
|
+
await this.recoveryKeySaver(newRecoveryKey)
|
|
1045
1203
|
|
|
1046
1204
|
await this.updateAuthFactors(
|
|
1047
1205
|
this.currentUMPToken.passwordSalt,
|
|
1048
1206
|
passwordKey,
|
|
1049
1207
|
presentationKey,
|
|
1050
|
-
|
|
1051
|
-
this.
|
|
1052
|
-
|
|
1208
|
+
newRecoveryKey, // Use the new key
|
|
1209
|
+
this.rootPrimaryKey,
|
|
1210
|
+
rootPrivilegedKey,
|
|
1211
|
+
this.profiles // Preserve profiles
|
|
1053
1212
|
)
|
|
1054
1213
|
}
|
|
1055
1214
|
|
|
1056
1215
|
/**
|
|
1057
1216
|
* Changes the user's presentation key.
|
|
1058
|
-
*
|
|
1059
|
-
* @param presentationKey The new presentation key (32 bytes).
|
|
1060
|
-
* @throws {Error} If the user is not authenticated, or if underlying token references are missing.
|
|
1061
1217
|
*/
|
|
1062
|
-
async changePresentationKey(
|
|
1063
|
-
if (!this.authenticated) {
|
|
1064
|
-
throw new Error('Not authenticated.')
|
|
1218
|
+
async changePresentationKey(newPresentationKey: number[]): Promise<void> {
|
|
1219
|
+
if (!this.authenticated || !this.currentUMPToken || !this.rootPrimaryKey || !this.rootPrivilegedKeyManager) {
|
|
1220
|
+
throw new Error('Not authenticated or missing required data.')
|
|
1065
1221
|
}
|
|
1066
|
-
if (
|
|
1067
|
-
throw new Error('
|
|
1222
|
+
if (newPresentationKey.length !== 32) {
|
|
1223
|
+
throw new Error('Presentation key must be 32 bytes.')
|
|
1068
1224
|
}
|
|
1069
1225
|
|
|
1070
|
-
// Decrypt existing
|
|
1071
|
-
const recoveryKey = (
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
protocolID: [2, 'admin key wrapping'],
|
|
1075
|
-
keyID: '1'
|
|
1076
|
-
})
|
|
1077
|
-
).plaintext
|
|
1078
|
-
const passwordKey = (
|
|
1079
|
-
await this.underlyingPrivilegedKeyManager!.decrypt({
|
|
1080
|
-
ciphertext: this.currentUMPToken.passwordKeyEncrypted,
|
|
1081
|
-
protocolID: [2, 'admin key wrapping'],
|
|
1082
|
-
keyID: '1'
|
|
1083
|
-
})
|
|
1084
|
-
).plaintext
|
|
1085
|
-
const privilegedKey = new SymmetricKey(this.XOR(passwordKey, this.primaryKey!)).decrypt(
|
|
1086
|
-
this.currentUMPToken.passwordPrimaryPrivileged
|
|
1087
|
-
) as number[]
|
|
1226
|
+
// Decrypt existing factors
|
|
1227
|
+
const recoveryKey = await this.getFactor('recoveryKey')
|
|
1228
|
+
const passwordKey = await this.getFactor('passwordKey')
|
|
1229
|
+
const rootPrivilegedKey = await this.getFactor('privilegedKey', true) // Get ROOT privileged key
|
|
1088
1230
|
|
|
1089
1231
|
await this.updateAuthFactors(
|
|
1090
1232
|
this.currentUMPToken.passwordSalt,
|
|
1091
1233
|
passwordKey,
|
|
1092
|
-
|
|
1234
|
+
newPresentationKey, // Use the new key
|
|
1093
1235
|
recoveryKey,
|
|
1094
|
-
this.
|
|
1095
|
-
|
|
1236
|
+
this.rootPrimaryKey,
|
|
1237
|
+
rootPrivilegedKey,
|
|
1238
|
+
this.profiles // Preserve profiles
|
|
1096
1239
|
)
|
|
1240
|
+
// Update the temporarily stored key if it was set
|
|
1241
|
+
if (this.presentationKey) {
|
|
1242
|
+
this.presentationKey = newPresentationKey
|
|
1243
|
+
}
|
|
1097
1244
|
}
|
|
1098
1245
|
|
|
1246
|
+
// --- Internal Helper Methods ---
|
|
1247
|
+
|
|
1099
1248
|
/**
|
|
1100
|
-
*
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1249
|
+
* Performs XOR operation on two byte arrays.
|
|
1250
|
+
*/
|
|
1251
|
+
private XOR(n1: number[], n2: number[]): number[] {
|
|
1252
|
+
if (n1.length !== n2.length) {
|
|
1253
|
+
// Provide more context in error
|
|
1254
|
+
throw new Error(`XOR length mismatch: ${n1.length} vs ${n2.length}`)
|
|
1255
|
+
}
|
|
1256
|
+
const r = new Array<number>(n1.length)
|
|
1257
|
+
for (let i = 0; i < n1.length; i++) {
|
|
1258
|
+
r[i] = n1[i] ^ n2[i]
|
|
1259
|
+
}
|
|
1260
|
+
return r
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Helper to decrypt a specific factor (key) stored encrypted in the UMP token.
|
|
1265
|
+
* Requires the root privileged key manager.
|
|
1266
|
+
* @param factorName Name of the factor to decrypt ('passwordKey', 'presentationKey', 'recoveryKey', 'privilegedKey').
|
|
1267
|
+
* @param getRoot If true and factorName is 'privilegedKey', returns the root privileged key bytes directly.
|
|
1268
|
+
* @returns The decrypted key bytes.
|
|
1269
|
+
*/
|
|
1270
|
+
private async getFactor(
|
|
1271
|
+
factorName: 'passwordKey' | 'presentationKey' | 'recoveryKey' | 'privilegedKey',
|
|
1272
|
+
getRoot: boolean = false
|
|
1273
|
+
): Promise<number[]> {
|
|
1274
|
+
if (!this.authenticated || !this.currentUMPToken || !this.rootPrivilegedKeyManager) {
|
|
1275
|
+
throw new Error(`Cannot get factor "${factorName}": Wallet not ready.`)
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
const protocolID: [0 | 1 | 2, string] = [2, 'admin key wrapping'] // Protocol used for encrypting factors
|
|
1279
|
+
const keyID = '1' // Key ID used
|
|
1280
|
+
|
|
1281
|
+
try {
|
|
1282
|
+
switch (factorName) {
|
|
1283
|
+
case 'passwordKey':
|
|
1284
|
+
return (
|
|
1285
|
+
await this.rootPrivilegedKeyManager.decrypt({
|
|
1286
|
+
ciphertext: this.currentUMPToken.passwordKeyEncrypted,
|
|
1287
|
+
protocolID,
|
|
1288
|
+
keyID
|
|
1289
|
+
})
|
|
1290
|
+
).plaintext
|
|
1291
|
+
case 'presentationKey':
|
|
1292
|
+
return (
|
|
1293
|
+
await this.rootPrivilegedKeyManager.decrypt({
|
|
1294
|
+
ciphertext: this.currentUMPToken.presentationKeyEncrypted,
|
|
1295
|
+
protocolID,
|
|
1296
|
+
keyID
|
|
1297
|
+
})
|
|
1298
|
+
).plaintext
|
|
1299
|
+
case 'recoveryKey':
|
|
1300
|
+
return (
|
|
1301
|
+
await this.rootPrivilegedKeyManager.decrypt({
|
|
1302
|
+
ciphertext: this.currentUMPToken.recoveryKeyEncrypted,
|
|
1303
|
+
protocolID,
|
|
1304
|
+
keyID
|
|
1305
|
+
})
|
|
1306
|
+
).plaintext
|
|
1307
|
+
case 'privilegedKey': {
|
|
1308
|
+
// This needs careful handling based on whether the ROOT or PROFILE key is needed.
|
|
1309
|
+
// This helper is mostly used for UMP updates, which need the ROOT key.
|
|
1310
|
+
// We retrieve the PrivateKey object first.
|
|
1311
|
+
const pk = await (this.rootPrivilegedKeyManager as any).getPrivilegedKey('UMP token update', true) // Force retrieval of root key
|
|
1312
|
+
return pk.toArray() // Return bytes
|
|
1313
|
+
}
|
|
1314
|
+
default:
|
|
1315
|
+
throw new Error(`Unknown factor name: ${factorName}`)
|
|
1316
|
+
}
|
|
1317
|
+
} catch (error) {
|
|
1318
|
+
console.error(`Error decrypting factor ${factorName}:`, error)
|
|
1319
|
+
throw new Error(`Failed to decrypt factor "${factorName}": ${(error as Error).message}`)
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* Recomputes UMP token fields with updated factors and profiles, then publishes the update.
|
|
1325
|
+
* This operation requires the *root* privileged key and the *default* profile wallet.
|
|
1109
1326
|
*/
|
|
1110
1327
|
private async updateAuthFactors(
|
|
1111
1328
|
passwordSalt: number[],
|
|
1112
1329
|
passwordKey: number[],
|
|
1113
1330
|
presentationKey: number[],
|
|
1114
1331
|
recoveryKey: number[],
|
|
1115
|
-
|
|
1116
|
-
|
|
1332
|
+
rootPrimaryKey: number[],
|
|
1333
|
+
rootPrivilegedKey: number[], // Explicitly pass the root key bytes
|
|
1334
|
+
profiles?: Profile[] // Pass current/new profiles list
|
|
1117
1335
|
): Promise<void> {
|
|
1118
|
-
if (!this.authenticated || !this.
|
|
1119
|
-
throw new Error('Wallet is not properly authenticated or missing data.')
|
|
1336
|
+
if (!this.authenticated || !this.rootPrimaryKey || !this.currentUMPToken) {
|
|
1337
|
+
throw new Error('Wallet is not properly authenticated or missing data for update.')
|
|
1338
|
+
}
|
|
1339
|
+
// Ensure we have the OLD token to consume
|
|
1340
|
+
const oldTokenToConsume = { ...this.currentUMPToken }
|
|
1341
|
+
if (!oldTokenToConsume.currentOutpoint) {
|
|
1342
|
+
throw new Error('Cannot update UMP token: Old token has no outpoint.')
|
|
1120
1343
|
}
|
|
1121
1344
|
|
|
1122
|
-
// Derive symmetrical encryption keys
|
|
1345
|
+
// Derive symmetrical encryption keys using XOR for the *root* keys
|
|
1123
1346
|
const presentationPassword = new SymmetricKey(this.XOR(presentationKey, passwordKey))
|
|
1124
1347
|
const presentationRecovery = new SymmetricKey(this.XOR(presentationKey, recoveryKey))
|
|
1125
1348
|
const recoveryPassword = new SymmetricKey(this.XOR(recoveryKey, passwordKey))
|
|
1126
|
-
const primaryPassword = new SymmetricKey(this.XOR(
|
|
1127
|
-
|
|
1128
|
-
// Build a temporary privileged key manager
|
|
1129
|
-
const
|
|
1349
|
+
const primaryPassword = new SymmetricKey(this.XOR(rootPrimaryKey, passwordKey)) // Use rootPrimaryKey
|
|
1350
|
+
|
|
1351
|
+
// Build a temporary privileged key manager using the explicit ROOT privileged key
|
|
1352
|
+
const tempRootPrivilegedKeyManager = new PrivilegedKeyManager(async () => new PrivateKey(rootPrivilegedKey))
|
|
1353
|
+
|
|
1354
|
+
// Encrypt profiles if provided
|
|
1355
|
+
let profilesEncrypted: number[] | undefined
|
|
1356
|
+
if (profiles && profiles.length > 0) {
|
|
1357
|
+
const profilesJson = JSON.stringify(profiles)
|
|
1358
|
+
const profilesBytes = Utils.toArray(profilesJson, 'utf8')
|
|
1359
|
+
profilesEncrypted = (
|
|
1360
|
+
await tempRootPrivilegedKeyManager.encrypt({
|
|
1361
|
+
plaintext: profilesBytes,
|
|
1362
|
+
protocolID: [2, 'admin profile wrapping'], // Separate protocol for profiles
|
|
1363
|
+
keyID: '1'
|
|
1364
|
+
})
|
|
1365
|
+
).ciphertext
|
|
1366
|
+
}
|
|
1130
1367
|
|
|
1131
|
-
// Construct the new UMP token
|
|
1132
|
-
const
|
|
1368
|
+
// Construct the new UMP token data
|
|
1369
|
+
const newTokenData: UMPToken = {
|
|
1133
1370
|
passwordSalt,
|
|
1134
|
-
passwordPresentationPrimary: presentationPassword.encrypt(
|
|
1135
|
-
passwordRecoveryPrimary: recoveryPassword.encrypt(
|
|
1136
|
-
presentationRecoveryPrimary: presentationRecovery.encrypt(
|
|
1137
|
-
passwordPrimaryPrivileged: primaryPassword.encrypt(
|
|
1138
|
-
presentationRecoveryPrivileged: presentationRecovery.encrypt(
|
|
1371
|
+
passwordPresentationPrimary: presentationPassword.encrypt(rootPrimaryKey) as number[],
|
|
1372
|
+
passwordRecoveryPrimary: recoveryPassword.encrypt(rootPrimaryKey) as number[],
|
|
1373
|
+
presentationRecoveryPrimary: presentationRecovery.encrypt(rootPrimaryKey) as number[],
|
|
1374
|
+
passwordPrimaryPrivileged: primaryPassword.encrypt(rootPrivilegedKey) as number[],
|
|
1375
|
+
presentationRecoveryPrivileged: presentationRecovery.encrypt(rootPrivilegedKey) as number[],
|
|
1139
1376
|
presentationHash: Hash.sha256(presentationKey),
|
|
1140
1377
|
recoveryHash: Hash.sha256(recoveryKey),
|
|
1141
1378
|
presentationKeyEncrypted: (
|
|
1142
|
-
await
|
|
1379
|
+
await tempRootPrivilegedKeyManager.encrypt({
|
|
1143
1380
|
plaintext: presentationKey,
|
|
1144
1381
|
protocolID: [2, 'admin key wrapping'],
|
|
1145
1382
|
keyID: '1'
|
|
1146
1383
|
})
|
|
1147
1384
|
).ciphertext,
|
|
1148
1385
|
passwordKeyEncrypted: (
|
|
1149
|
-
await
|
|
1386
|
+
await tempRootPrivilegedKeyManager.encrypt({
|
|
1150
1387
|
plaintext: passwordKey,
|
|
1151
1388
|
protocolID: [2, 'admin key wrapping'],
|
|
1152
1389
|
keyID: '1'
|
|
1153
1390
|
})
|
|
1154
1391
|
).ciphertext,
|
|
1155
1392
|
recoveryKeyEncrypted: (
|
|
1156
|
-
await
|
|
1393
|
+
await tempRootPrivilegedKeyManager.encrypt({
|
|
1157
1394
|
plaintext: recoveryKey,
|
|
1158
1395
|
protocolID: [2, 'admin key wrapping'],
|
|
1159
1396
|
keyID: '1'
|
|
1160
1397
|
})
|
|
1161
|
-
).ciphertext
|
|
1398
|
+
).ciphertext,
|
|
1399
|
+
profilesEncrypted // Add encrypted profiles
|
|
1400
|
+
// currentOutpoint will be set after publishing
|
|
1162
1401
|
}
|
|
1163
1402
|
|
|
1164
|
-
//
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
newToken,
|
|
1169
|
-
this.currentUMPToken
|
|
1170
|
-
)
|
|
1171
|
-
this.currentUMPToken = newToken
|
|
1172
|
-
}
|
|
1403
|
+
// We need the wallet built for the DEFAULT profile to publish the UMP token.
|
|
1404
|
+
// If the current active profile is not default, temporarily switch, publish, then switch back.
|
|
1405
|
+
const currentActiveId = this.activeProfileId
|
|
1406
|
+
let walletToUse: WalletInterface | undefined = this.underlying
|
|
1173
1407
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
* @param n2 The second byte array.
|
|
1179
|
-
* @returns A new byte array which is the element-wise XOR of the two inputs.
|
|
1180
|
-
* @throws {Error} if the two arrays are not the same length.
|
|
1181
|
-
*/
|
|
1182
|
-
private XOR(n1: number[], n2: number[]): number[] {
|
|
1183
|
-
if (n1.length !== n2.length) {
|
|
1184
|
-
throw new Error('lengths mismatch')
|
|
1408
|
+
if (currentActiveId.every(x => x === 0)) {
|
|
1409
|
+
console.log('Temporarily switching to default profile to update UMP token...')
|
|
1410
|
+
await this.switchProfile(DEFAULT_PROFILE_ID) // This rebuilds this.underlying
|
|
1411
|
+
walletToUse = this.underlying
|
|
1185
1412
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1413
|
+
|
|
1414
|
+
if (!walletToUse) {
|
|
1415
|
+
throw new Error('Default profile wallet could not be activated for UMP token update.')
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Publish the new token on-chain, consuming the old one
|
|
1419
|
+
try {
|
|
1420
|
+
newTokenData.currentOutpoint = await this.UMPTokenInteractor.buildAndSend(
|
|
1421
|
+
walletToUse, // Use the (potentially temporarily activated) default profile wallet
|
|
1422
|
+
this.adminOriginator,
|
|
1423
|
+
newTokenData,
|
|
1424
|
+
oldTokenToConsume // Consume the previous token
|
|
1425
|
+
)
|
|
1426
|
+
// Update the manager's state
|
|
1427
|
+
this.currentUMPToken = newTokenData
|
|
1428
|
+
// Profiles are already updated in this.profiles if they were passed in
|
|
1429
|
+
} finally {
|
|
1430
|
+
// Switch back if we temporarily switched
|
|
1431
|
+
if (!currentActiveId.every(x => x === 0)) {
|
|
1432
|
+
console.log('Switching back to original profile...')
|
|
1433
|
+
await this.switchProfile(currentActiveId)
|
|
1434
|
+
}
|
|
1189
1435
|
}
|
|
1190
|
-
return r
|
|
1191
1436
|
}
|
|
1192
1437
|
|
|
1193
1438
|
/**
|
|
1194
|
-
*
|
|
1195
|
-
*
|
|
1196
|
-
* - [1 byte version (value=1)]
|
|
1197
|
-
* - For each array field in the UMP token, [varint length + bytes]
|
|
1198
|
-
* - Then [varint length + outpoint string in UTF-8]
|
|
1199
|
-
*
|
|
1200
|
-
* @param token The UMP token to serialize.
|
|
1201
|
-
* @returns A byte array representing the serialized token.
|
|
1202
|
-
* @throws {Error} if the token has no currentOutpoint (required for serialization).
|
|
1439
|
+
* Serializes a UMP token to binary format (Version 2 with optional profiles).
|
|
1440
|
+
* 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]
|
|
1203
1441
|
*/
|
|
1204
1442
|
private serializeUMPToken(token: UMPToken): number[] {
|
|
1205
1443
|
if (!token.currentOutpoint) {
|
|
@@ -1207,29 +1445,35 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1207
1445
|
}
|
|
1208
1446
|
|
|
1209
1447
|
const writer = new Utils.Writer()
|
|
1210
|
-
//
|
|
1211
|
-
writer.writeUInt8(1)
|
|
1448
|
+
writer.writeUInt8(2) // Version 2
|
|
1212
1449
|
|
|
1213
|
-
// Helper to write array with length prefix
|
|
1214
1450
|
const writeArray = (arr: number[]) => {
|
|
1215
1451
|
writer.writeVarIntNum(arr.length)
|
|
1216
1452
|
writer.write(arr)
|
|
1217
1453
|
}
|
|
1218
1454
|
|
|
1219
|
-
// Write
|
|
1220
|
-
writeArray(token.
|
|
1221
|
-
writeArray(token.
|
|
1222
|
-
writeArray(token.
|
|
1223
|
-
writeArray(token.
|
|
1224
|
-
writeArray(token.
|
|
1225
|
-
writeArray(token.
|
|
1226
|
-
writeArray(token.
|
|
1227
|
-
writeArray(token.recoveryHash)
|
|
1228
|
-
writeArray(token.presentationKeyEncrypted)
|
|
1229
|
-
writeArray(token.
|
|
1230
|
-
writeArray(token.
|
|
1231
|
-
|
|
1232
|
-
//
|
|
1455
|
+
// Write standard fields in specific order
|
|
1456
|
+
writeArray(token.passwordSalt) // 0
|
|
1457
|
+
writeArray(token.passwordPresentationPrimary) // 1
|
|
1458
|
+
writeArray(token.passwordRecoveryPrimary) // 2
|
|
1459
|
+
writeArray(token.presentationRecoveryPrimary) // 3
|
|
1460
|
+
writeArray(token.passwordPrimaryPrivileged) // 4
|
|
1461
|
+
writeArray(token.presentationRecoveryPrivileged) // 5
|
|
1462
|
+
writeArray(token.presentationHash) // 6
|
|
1463
|
+
writeArray(token.recoveryHash) // 7
|
|
1464
|
+
writeArray(token.presentationKeyEncrypted) // 8
|
|
1465
|
+
writeArray(token.passwordKeyEncrypted) // 9 - Swapped order vs original doc comment
|
|
1466
|
+
writeArray(token.recoveryKeyEncrypted) // 10
|
|
1467
|
+
|
|
1468
|
+
// Write optional profiles field
|
|
1469
|
+
if (token.profilesEncrypted && token.profilesEncrypted.length > 0) {
|
|
1470
|
+
writer.writeUInt8(1) // Flag indicating profiles present
|
|
1471
|
+
writeArray(token.profilesEncrypted)
|
|
1472
|
+
} else {
|
|
1473
|
+
writer.writeUInt8(0) // Flag indicating no profiles
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// Write outpoint string
|
|
1233
1477
|
const outpointBytes = Utils.toArray(token.currentOutpoint, 'utf8')
|
|
1234
1478
|
writer.writeVarIntNum(outpointBytes.length)
|
|
1235
1479
|
writer.write(outpointBytes)
|
|
@@ -1238,57 +1482,61 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1238
1482
|
}
|
|
1239
1483
|
|
|
1240
1484
|
/**
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
* @param bin The serialized byte array.
|
|
1244
|
-
* @returns The reconstructed UMP token.
|
|
1245
|
-
* @throws {Error} if the version byte is unexpected or if parsing fails.
|
|
1485
|
+
* Deserializes a UMP token from binary format (Handles Version 1 and 2).
|
|
1246
1486
|
*/
|
|
1247
1487
|
private deserializeUMPToken(bin: number[]): UMPToken {
|
|
1248
1488
|
const reader = new Utils.Reader(bin)
|
|
1249
|
-
|
|
1250
|
-
// Check version:
|
|
1251
1489
|
const version = reader.readUInt8()
|
|
1252
|
-
|
|
1253
|
-
|
|
1490
|
+
|
|
1491
|
+
if (version !== 1 && version !== 2) {
|
|
1492
|
+
throw new Error(`Unsupported UMP token serialization version: ${version}`)
|
|
1254
1493
|
}
|
|
1255
1494
|
|
|
1256
|
-
// Helper to read an array with length prefix
|
|
1257
1495
|
const readArray = (): number[] => {
|
|
1258
1496
|
const length = reader.readVarIntNum()
|
|
1259
1497
|
return reader.read(length)
|
|
1260
1498
|
}
|
|
1261
1499
|
|
|
1262
|
-
// Read
|
|
1263
|
-
const
|
|
1264
|
-
const
|
|
1265
|
-
const
|
|
1266
|
-
const
|
|
1267
|
-
const
|
|
1268
|
-
const
|
|
1269
|
-
const
|
|
1270
|
-
const recoveryHash = readArray()
|
|
1271
|
-
const presentationKeyEncrypted = readArray()
|
|
1272
|
-
const
|
|
1273
|
-
const
|
|
1274
|
-
|
|
1275
|
-
// Read
|
|
1500
|
+
// Read standard fields (order matches serialization V2)
|
|
1501
|
+
const passwordSalt = readArray() // 0
|
|
1502
|
+
const passwordPresentationPrimary = readArray() // 1
|
|
1503
|
+
const passwordRecoveryPrimary = readArray() // 2
|
|
1504
|
+
const presentationRecoveryPrimary = readArray() // 3
|
|
1505
|
+
const passwordPrimaryPrivileged = readArray() // 4
|
|
1506
|
+
const presentationRecoveryPrivileged = readArray() // 5
|
|
1507
|
+
const presentationHash = readArray() // 6
|
|
1508
|
+
const recoveryHash = readArray() // 7
|
|
1509
|
+
const presentationKeyEncrypted = readArray() // 8
|
|
1510
|
+
const passwordKeyEncrypted = readArray() // 9
|
|
1511
|
+
const recoveryKeyEncrypted = readArray() // 10
|
|
1512
|
+
|
|
1513
|
+
// Read optional profiles (only in V2)
|
|
1514
|
+
let profilesEncrypted: number[] | undefined
|
|
1515
|
+
if (version === 2) {
|
|
1516
|
+
const profilesFlag = reader.readUInt8()
|
|
1517
|
+
if (profilesFlag === 1) {
|
|
1518
|
+
profilesEncrypted = readArray()
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Read outpoint string
|
|
1276
1523
|
const outpointLen = reader.readVarIntNum()
|
|
1277
1524
|
const outpointBytes = reader.read(outpointLen)
|
|
1278
1525
|
const currentOutpoint = Utils.toUTF8(outpointBytes)
|
|
1279
1526
|
|
|
1280
1527
|
const token: UMPToken = {
|
|
1528
|
+
passwordSalt,
|
|
1281
1529
|
passwordPresentationPrimary,
|
|
1282
1530
|
passwordRecoveryPrimary,
|
|
1283
1531
|
presentationRecoveryPrimary,
|
|
1284
1532
|
passwordPrimaryPrivileged,
|
|
1285
1533
|
presentationRecoveryPrivileged,
|
|
1286
1534
|
presentationHash,
|
|
1287
|
-
passwordSalt,
|
|
1288
1535
|
recoveryHash,
|
|
1289
1536
|
presentationKeyEncrypted,
|
|
1537
|
+
passwordKeyEncrypted, // Corrected order
|
|
1290
1538
|
recoveryKeyEncrypted,
|
|
1291
|
-
|
|
1539
|
+
profilesEncrypted, // May be undefined
|
|
1292
1540
|
currentOutpoint
|
|
1293
1541
|
}
|
|
1294
1542
|
|
|
@@ -1296,28 +1544,37 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1296
1544
|
}
|
|
1297
1545
|
|
|
1298
1546
|
/**
|
|
1299
|
-
*
|
|
1547
|
+
* Sets up the root key infrastructure after authentication or loading from snapshot.
|
|
1548
|
+
* Initializes the root primary key, root privileged key manager, loads profiles,
|
|
1549
|
+
* and sets the authenticated flag. Does NOT switch profile initially.
|
|
1300
1550
|
*
|
|
1301
|
-
* @param
|
|
1302
|
-
* @param
|
|
1551
|
+
* @param rootPrimaryKey The user's root primary key (32 bytes).
|
|
1552
|
+
* @param ephemeralRootPrivilegedKey Optional root privileged key (e.g., during recovery flows).
|
|
1303
1553
|
*/
|
|
1304
|
-
private async
|
|
1554
|
+
private async setupRootInfrastructure(
|
|
1555
|
+
rootPrimaryKey: number[],
|
|
1556
|
+
ephemeralRootPrivilegedKey?: number[]
|
|
1557
|
+
): Promise<void> {
|
|
1305
1558
|
if (!this.currentUMPToken) {
|
|
1306
|
-
throw new Error('A UMP token must exist before
|
|
1559
|
+
throw new Error('A UMP token must exist before setting up root infrastructure!')
|
|
1307
1560
|
}
|
|
1561
|
+
this.rootPrimaryKey = rootPrimaryKey
|
|
1308
1562
|
|
|
1309
|
-
|
|
1563
|
+
// Store ephemeral key if provided, for one-time use by the manager
|
|
1564
|
+
let oneTimePrivilegedKey: PrivateKey | undefined = ephemeralRootPrivilegedKey
|
|
1565
|
+
? new PrivateKey(ephemeralRootPrivilegedKey)
|
|
1566
|
+
: undefined
|
|
1310
1567
|
|
|
1311
|
-
// Create
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
if (
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
privilegedKey = undefined
|
|
1568
|
+
// Create the ROOT PrivilegedKeyManager
|
|
1569
|
+
this.rootPrivilegedKeyManager = new PrivilegedKeyManager(async (reason: string) => {
|
|
1570
|
+
// 1. Use one-time key if available (for recovery)
|
|
1571
|
+
if (oneTimePrivilegedKey) {
|
|
1572
|
+
const tempKey = oneTimePrivilegedKey
|
|
1573
|
+
oneTimePrivilegedKey = undefined // Consume it
|
|
1318
1574
|
return tempKey
|
|
1319
1575
|
}
|
|
1320
|
-
|
|
1576
|
+
|
|
1577
|
+
// 2. Otherwise, derive from password
|
|
1321
1578
|
const password = await this.passwordRetriever(reason, (passwordCandidate: string) => {
|
|
1322
1579
|
try {
|
|
1323
1580
|
const derivedPasswordKey = Hash.pbkdf2(
|
|
@@ -1327,19 +1584,17 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1327
1584
|
32,
|
|
1328
1585
|
'sha512'
|
|
1329
1586
|
)
|
|
1330
|
-
|
|
1331
|
-
const privilegedDecryptor = this.XOR(this.primaryKey!, derivedPasswordKey)
|
|
1587
|
+
const privilegedDecryptor = this.XOR(this.rootPrimaryKey!, derivedPasswordKey)
|
|
1332
1588
|
const decryptedPrivileged = new SymmetricKey(privilegedDecryptor).decrypt(
|
|
1333
1589
|
this.currentUMPToken!.passwordPrimaryPrivileged
|
|
1334
1590
|
) as number[]
|
|
1335
|
-
|
|
1336
|
-
return true
|
|
1337
|
-
}
|
|
1338
|
-
return false
|
|
1591
|
+
return !!decryptedPrivileged // Test passes if decryption works
|
|
1339
1592
|
} catch (e) {
|
|
1340
1593
|
return false
|
|
1341
1594
|
}
|
|
1342
1595
|
})
|
|
1596
|
+
|
|
1597
|
+
// Decrypt the root privileged key using the confirmed password
|
|
1343
1598
|
const derivedPasswordKey = Hash.pbkdf2(
|
|
1344
1599
|
Utils.toArray(password, 'utf8'),
|
|
1345
1600
|
this.currentUMPToken!.passwordSalt,
|
|
@@ -1347,39 +1602,67 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1347
1602
|
32,
|
|
1348
1603
|
'sha512'
|
|
1349
1604
|
)
|
|
1350
|
-
|
|
1351
|
-
const
|
|
1352
|
-
const decryptedPrivileged = new SymmetricKey(privilegedDecryptor).decrypt(
|
|
1605
|
+
const privilegedDecryptor = this.XOR(this.rootPrimaryKey!, derivedPasswordKey)
|
|
1606
|
+
const rootPrivilegedBytes = new SymmetricKey(privilegedDecryptor).decrypt(
|
|
1353
1607
|
this.currentUMPToken!.passwordPrimaryPrivileged
|
|
1354
1608
|
) as number[]
|
|
1355
|
-
return new PrivateKey(decryptedPrivileged)
|
|
1356
|
-
})
|
|
1357
1609
|
|
|
1358
|
-
|
|
1610
|
+
return new PrivateKey(rootPrivilegedBytes) // Return the ROOT key object
|
|
1611
|
+
})
|
|
1359
1612
|
|
|
1360
|
-
//
|
|
1361
|
-
this.
|
|
1613
|
+
// Decrypt and load profiles if present in the token
|
|
1614
|
+
this.profiles = [] // Clear existing profiles before loading
|
|
1615
|
+
if (this.currentUMPToken.profilesEncrypted && this.currentUMPToken.profilesEncrypted.length > 0) {
|
|
1616
|
+
try {
|
|
1617
|
+
const decryptedProfileBytes = (
|
|
1618
|
+
await this.rootPrivilegedKeyManager.decrypt({
|
|
1619
|
+
ciphertext: this.currentUMPToken.profilesEncrypted,
|
|
1620
|
+
protocolID: [2, 'admin profile wrapping'], // Use profile protocol ID
|
|
1621
|
+
keyID: '1'
|
|
1622
|
+
})
|
|
1623
|
+
).plaintext
|
|
1624
|
+
const profilesJson = Utils.toUTF8(decryptedProfileBytes)
|
|
1625
|
+
this.profiles = JSON.parse(profilesJson) as Profile[]
|
|
1626
|
+
} catch (error) {
|
|
1627
|
+
console.error('Failed to decrypt or parse profiles:', error)
|
|
1628
|
+
// Decide if this should be fatal or just log and continue without profiles
|
|
1629
|
+
this.profiles = [] // Ensure profiles are empty on error
|
|
1630
|
+
// Optionally re-throw or handle more gracefully
|
|
1631
|
+
throw new Error(`Failed to load profiles: ${(error as Error).message}`)
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1362
1634
|
|
|
1363
1635
|
this.authenticated = true
|
|
1636
|
+
// Note: We don't call switchProfile here anymore.
|
|
1637
|
+
// It's called by the auth methods (providePassword/provideRecoveryKey) or loadSnapshot after this.
|
|
1364
1638
|
}
|
|
1365
1639
|
|
|
1366
1640
|
/*
|
|
1367
1641
|
* ---------------------------------------------------------------------------------------
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
1642
|
+
* Standard WalletInterface methods proxying to the *active* underlying wallet.
|
|
1643
|
+
* Includes authentication checks and admin originator protection.
|
|
1370
1644
|
* ---------------------------------------------------------------------------------------
|
|
1371
1645
|
*/
|
|
1372
1646
|
|
|
1373
|
-
|
|
1374
|
-
args: GetPublicKeyArgs,
|
|
1375
|
-
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1376
|
-
): Promise<GetPublicKeyResult> {
|
|
1647
|
+
private checkAuthAndUnderlying(originator?: string): void {
|
|
1377
1648
|
if (!this.authenticated) {
|
|
1378
1649
|
throw new Error('User is not authenticated.')
|
|
1379
1650
|
}
|
|
1651
|
+
if (!this.underlying) {
|
|
1652
|
+
// This might happen if authentication succeeded but profile switching failed
|
|
1653
|
+
throw new Error('Underlying wallet for the active profile is not initialized.')
|
|
1654
|
+
}
|
|
1380
1655
|
if (originator === this.adminOriginator) {
|
|
1381
1656
|
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1382
1657
|
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// Example proxy method (repeat pattern for all others)
|
|
1661
|
+
async getPublicKey(
|
|
1662
|
+
args: GetPublicKeyArgs,
|
|
1663
|
+
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1664
|
+
): Promise<GetPublicKeyResult> {
|
|
1665
|
+
this.checkAuthAndUnderlying(originator)
|
|
1383
1666
|
return this.underlying!.getPublicKey(args, originator)
|
|
1384
1667
|
}
|
|
1385
1668
|
|
|
@@ -1387,12 +1670,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1387
1670
|
args: RevealCounterpartyKeyLinkageArgs,
|
|
1388
1671
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1389
1672
|
): Promise<RevealCounterpartyKeyLinkageResult> {
|
|
1390
|
-
|
|
1391
|
-
throw new Error('User is not authenticated.')
|
|
1392
|
-
}
|
|
1393
|
-
if (originator === this.adminOriginator) {
|
|
1394
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1395
|
-
}
|
|
1673
|
+
this.checkAuthAndUnderlying(originator)
|
|
1396
1674
|
return this.underlying!.revealCounterpartyKeyLinkage(args, originator)
|
|
1397
1675
|
}
|
|
1398
1676
|
|
|
@@ -1400,12 +1678,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1400
1678
|
args: RevealSpecificKeyLinkageArgs,
|
|
1401
1679
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1402
1680
|
): Promise<RevealSpecificKeyLinkageResult> {
|
|
1403
|
-
|
|
1404
|
-
throw new Error('User is not authenticated.')
|
|
1405
|
-
}
|
|
1406
|
-
if (originator === this.adminOriginator) {
|
|
1407
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1408
|
-
}
|
|
1681
|
+
this.checkAuthAndUnderlying(originator)
|
|
1409
1682
|
return this.underlying!.revealSpecificKeyLinkage(args, originator)
|
|
1410
1683
|
}
|
|
1411
1684
|
|
|
@@ -1413,12 +1686,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1413
1686
|
args: WalletEncryptArgs,
|
|
1414
1687
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1415
1688
|
): Promise<WalletEncryptResult> {
|
|
1416
|
-
|
|
1417
|
-
throw new Error('User is not authenticated.')
|
|
1418
|
-
}
|
|
1419
|
-
if (originator === this.adminOriginator) {
|
|
1420
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1421
|
-
}
|
|
1689
|
+
this.checkAuthAndUnderlying(originator)
|
|
1422
1690
|
return this.underlying!.encrypt(args, originator)
|
|
1423
1691
|
}
|
|
1424
1692
|
|
|
@@ -1426,12 +1694,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1426
1694
|
args: WalletDecryptArgs,
|
|
1427
1695
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1428
1696
|
): Promise<WalletDecryptResult> {
|
|
1429
|
-
|
|
1430
|
-
throw new Error('User is not authenticated.')
|
|
1431
|
-
}
|
|
1432
|
-
if (originator === this.adminOriginator) {
|
|
1433
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1434
|
-
}
|
|
1697
|
+
this.checkAuthAndUnderlying(originator)
|
|
1435
1698
|
return this.underlying!.decrypt(args, originator)
|
|
1436
1699
|
}
|
|
1437
1700
|
|
|
@@ -1439,12 +1702,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1439
1702
|
args: CreateHmacArgs,
|
|
1440
1703
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1441
1704
|
): Promise<CreateHmacResult> {
|
|
1442
|
-
|
|
1443
|
-
throw new Error('User is not authenticated.')
|
|
1444
|
-
}
|
|
1445
|
-
if (originator === this.adminOriginator) {
|
|
1446
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1447
|
-
}
|
|
1705
|
+
this.checkAuthAndUnderlying(originator)
|
|
1448
1706
|
return this.underlying!.createHmac(args, originator)
|
|
1449
1707
|
}
|
|
1450
1708
|
|
|
@@ -1452,12 +1710,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1452
1710
|
args: VerifyHmacArgs,
|
|
1453
1711
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1454
1712
|
): Promise<VerifyHmacResult> {
|
|
1455
|
-
|
|
1456
|
-
throw new Error('User is not authenticated.')
|
|
1457
|
-
}
|
|
1458
|
-
if (originator === this.adminOriginator) {
|
|
1459
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1460
|
-
}
|
|
1713
|
+
this.checkAuthAndUnderlying(originator)
|
|
1461
1714
|
return this.underlying!.verifyHmac(args, originator)
|
|
1462
1715
|
}
|
|
1463
1716
|
|
|
@@ -1465,12 +1718,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1465
1718
|
args: CreateSignatureArgs,
|
|
1466
1719
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1467
1720
|
): Promise<CreateSignatureResult> {
|
|
1468
|
-
|
|
1469
|
-
throw new Error('User is not authenticated.')
|
|
1470
|
-
}
|
|
1471
|
-
if (originator === this.adminOriginator) {
|
|
1472
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1473
|
-
}
|
|
1721
|
+
this.checkAuthAndUnderlying(originator)
|
|
1474
1722
|
return this.underlying!.createSignature(args, originator)
|
|
1475
1723
|
}
|
|
1476
1724
|
|
|
@@ -1478,12 +1726,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1478
1726
|
args: VerifySignatureArgs,
|
|
1479
1727
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1480
1728
|
): Promise<VerifySignatureResult> {
|
|
1481
|
-
|
|
1482
|
-
throw new Error('User is not authenticated.')
|
|
1483
|
-
}
|
|
1484
|
-
if (originator === this.adminOriginator) {
|
|
1485
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1486
|
-
}
|
|
1729
|
+
this.checkAuthAndUnderlying(originator)
|
|
1487
1730
|
return this.underlying!.verifySignature(args, originator)
|
|
1488
1731
|
}
|
|
1489
1732
|
|
|
@@ -1491,12 +1734,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1491
1734
|
args: CreateActionArgs,
|
|
1492
1735
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1493
1736
|
): Promise<CreateActionResult> {
|
|
1494
|
-
|
|
1495
|
-
throw new Error('User is not authenticated.')
|
|
1496
|
-
}
|
|
1497
|
-
if (originator === this.adminOriginator) {
|
|
1498
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1499
|
-
}
|
|
1737
|
+
this.checkAuthAndUnderlying(originator)
|
|
1500
1738
|
return this.underlying!.createAction(args, originator)
|
|
1501
1739
|
}
|
|
1502
1740
|
|
|
@@ -1504,12 +1742,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1504
1742
|
args: SignActionArgs,
|
|
1505
1743
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1506
1744
|
): Promise<SignActionResult> {
|
|
1507
|
-
|
|
1508
|
-
throw new Error('User is not authenticated.')
|
|
1509
|
-
}
|
|
1510
|
-
if (originator === this.adminOriginator) {
|
|
1511
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1512
|
-
}
|
|
1745
|
+
this.checkAuthAndUnderlying(originator)
|
|
1513
1746
|
return this.underlying!.signAction(args, originator)
|
|
1514
1747
|
}
|
|
1515
1748
|
|
|
@@ -1517,12 +1750,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1517
1750
|
args: AbortActionArgs,
|
|
1518
1751
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1519
1752
|
): Promise<AbortActionResult> {
|
|
1520
|
-
|
|
1521
|
-
throw new Error('User is not authenticated.')
|
|
1522
|
-
}
|
|
1523
|
-
if (originator === this.adminOriginator) {
|
|
1524
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1525
|
-
}
|
|
1753
|
+
this.checkAuthAndUnderlying(originator)
|
|
1526
1754
|
return this.underlying!.abortAction(args, originator)
|
|
1527
1755
|
}
|
|
1528
1756
|
|
|
@@ -1530,12 +1758,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1530
1758
|
args: ListActionsArgs,
|
|
1531
1759
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1532
1760
|
): Promise<ListActionsResult> {
|
|
1533
|
-
|
|
1534
|
-
throw new Error('User is not authenticated.')
|
|
1535
|
-
}
|
|
1536
|
-
if (originator === this.adminOriginator) {
|
|
1537
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1538
|
-
}
|
|
1761
|
+
this.checkAuthAndUnderlying(originator)
|
|
1539
1762
|
return this.underlying!.listActions(args, originator)
|
|
1540
1763
|
}
|
|
1541
1764
|
|
|
@@ -1543,12 +1766,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1543
1766
|
args: InternalizeActionArgs,
|
|
1544
1767
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1545
1768
|
): Promise<InternalizeActionResult> {
|
|
1546
|
-
|
|
1547
|
-
throw new Error('User is not authenticated.')
|
|
1548
|
-
}
|
|
1549
|
-
if (originator === this.adminOriginator) {
|
|
1550
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1551
|
-
}
|
|
1769
|
+
this.checkAuthAndUnderlying(originator)
|
|
1552
1770
|
return this.underlying!.internalizeAction(args, originator)
|
|
1553
1771
|
}
|
|
1554
1772
|
|
|
@@ -1556,12 +1774,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1556
1774
|
args: ListOutputsArgs,
|
|
1557
1775
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1558
1776
|
): Promise<ListOutputsResult> {
|
|
1559
|
-
|
|
1560
|
-
throw new Error('User is not authenticated.')
|
|
1561
|
-
}
|
|
1562
|
-
if (originator === this.adminOriginator) {
|
|
1563
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1564
|
-
}
|
|
1777
|
+
this.checkAuthAndUnderlying(originator)
|
|
1565
1778
|
return this.underlying!.listOutputs(args, originator)
|
|
1566
1779
|
}
|
|
1567
1780
|
|
|
@@ -1569,12 +1782,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1569
1782
|
args: RelinquishOutputArgs,
|
|
1570
1783
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1571
1784
|
): Promise<RelinquishOutputResult> {
|
|
1572
|
-
|
|
1573
|
-
throw new Error('User is not authenticated.')
|
|
1574
|
-
}
|
|
1575
|
-
if (originator === this.adminOriginator) {
|
|
1576
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1577
|
-
}
|
|
1785
|
+
this.checkAuthAndUnderlying(originator)
|
|
1578
1786
|
return this.underlying!.relinquishOutput(args, originator)
|
|
1579
1787
|
}
|
|
1580
1788
|
|
|
@@ -1582,12 +1790,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1582
1790
|
args: AcquireCertificateArgs,
|
|
1583
1791
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1584
1792
|
): Promise<AcquireCertificateResult> {
|
|
1585
|
-
|
|
1586
|
-
throw new Error('User is not authenticated.')
|
|
1587
|
-
}
|
|
1588
|
-
if (originator === this.adminOriginator) {
|
|
1589
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1590
|
-
}
|
|
1793
|
+
this.checkAuthAndUnderlying(originator)
|
|
1591
1794
|
return this.underlying!.acquireCertificate(args, originator)
|
|
1592
1795
|
}
|
|
1593
1796
|
|
|
@@ -1595,12 +1798,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1595
1798
|
args: ListCertificatesArgs,
|
|
1596
1799
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1597
1800
|
): Promise<ListCertificatesResult> {
|
|
1598
|
-
|
|
1599
|
-
throw new Error('User is not authenticated.')
|
|
1600
|
-
}
|
|
1601
|
-
if (originator === this.adminOriginator) {
|
|
1602
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1603
|
-
}
|
|
1801
|
+
this.checkAuthAndUnderlying(originator)
|
|
1604
1802
|
return this.underlying!.listCertificates(args, originator)
|
|
1605
1803
|
}
|
|
1606
1804
|
|
|
@@ -1608,12 +1806,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1608
1806
|
args: ProveCertificateArgs,
|
|
1609
1807
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1610
1808
|
): Promise<ProveCertificateResult> {
|
|
1611
|
-
|
|
1612
|
-
throw new Error('User is not authenticated.')
|
|
1613
|
-
}
|
|
1614
|
-
if (originator === this.adminOriginator) {
|
|
1615
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1616
|
-
}
|
|
1809
|
+
this.checkAuthAndUnderlying(originator)
|
|
1617
1810
|
return this.underlying!.proveCertificate(args, originator)
|
|
1618
1811
|
}
|
|
1619
1812
|
|
|
@@ -1621,12 +1814,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1621
1814
|
args: RelinquishCertificateArgs,
|
|
1622
1815
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1623
1816
|
): Promise<RelinquishCertificateResult> {
|
|
1624
|
-
|
|
1625
|
-
throw new Error('User is not authenticated.')
|
|
1626
|
-
}
|
|
1627
|
-
if (originator === this.adminOriginator) {
|
|
1628
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1629
|
-
}
|
|
1817
|
+
this.checkAuthAndUnderlying(originator)
|
|
1630
1818
|
return this.underlying!.relinquishCertificate(args, originator)
|
|
1631
1819
|
}
|
|
1632
1820
|
|
|
@@ -1634,12 +1822,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1634
1822
|
args: DiscoverByIdentityKeyArgs,
|
|
1635
1823
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1636
1824
|
): Promise<DiscoverCertificatesResult> {
|
|
1637
|
-
|
|
1638
|
-
throw new Error('User is not authenticated.')
|
|
1639
|
-
}
|
|
1640
|
-
if (originator === this.adminOriginator) {
|
|
1641
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1642
|
-
}
|
|
1825
|
+
this.checkAuthAndUnderlying(originator)
|
|
1643
1826
|
return this.underlying!.discoverByIdentityKey(args, originator)
|
|
1644
1827
|
}
|
|
1645
1828
|
|
|
@@ -1647,12 +1830,7 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1647
1830
|
args: DiscoverByAttributesArgs,
|
|
1648
1831
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1649
1832
|
): Promise<DiscoverCertificatesResult> {
|
|
1650
|
-
|
|
1651
|
-
throw new Error('User is not authenticated.')
|
|
1652
|
-
}
|
|
1653
|
-
if (originator === this.adminOriginator) {
|
|
1654
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1655
|
-
}
|
|
1833
|
+
this.checkAuthAndUnderlying(originator)
|
|
1656
1834
|
return this.underlying!.discoverByAttributes(args, originator)
|
|
1657
1835
|
}
|
|
1658
1836
|
|
|
@@ -1673,19 +1851,14 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1673
1851
|
if (originator === this.adminOriginator) {
|
|
1674
1852
|
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1675
1853
|
}
|
|
1676
|
-
while (!this.authenticated) {
|
|
1854
|
+
while (!this.authenticated || !this.underlying) {
|
|
1677
1855
|
await new Promise(resolve => setTimeout(resolve, 100))
|
|
1678
1856
|
}
|
|
1679
1857
|
return { authenticated: true }
|
|
1680
1858
|
}
|
|
1681
1859
|
|
|
1682
1860
|
async getHeight(_: {}, originator?: OriginatorDomainNameStringUnder250Bytes): Promise<GetHeightResult> {
|
|
1683
|
-
|
|
1684
|
-
throw new Error('User is not authenticated.')
|
|
1685
|
-
}
|
|
1686
|
-
if (originator === this.adminOriginator) {
|
|
1687
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1688
|
-
}
|
|
1861
|
+
this.checkAuthAndUnderlying(originator)
|
|
1689
1862
|
return this.underlying!.getHeight({}, originator)
|
|
1690
1863
|
}
|
|
1691
1864
|
|
|
@@ -1693,32 +1866,17 @@ export class CWIStyleWalletManager implements WalletInterface {
|
|
|
1693
1866
|
args: GetHeaderArgs,
|
|
1694
1867
|
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
1695
1868
|
): Promise<GetHeaderResult> {
|
|
1696
|
-
|
|
1697
|
-
throw new Error('User is not authenticated.')
|
|
1698
|
-
}
|
|
1699
|
-
if (originator === this.adminOriginator) {
|
|
1700
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1701
|
-
}
|
|
1869
|
+
this.checkAuthAndUnderlying(originator)
|
|
1702
1870
|
return this.underlying!.getHeaderForHeight(args, originator)
|
|
1703
1871
|
}
|
|
1704
1872
|
|
|
1705
1873
|
async getNetwork(_: {}, originator?: OriginatorDomainNameStringUnder250Bytes): Promise<GetNetworkResult> {
|
|
1706
|
-
|
|
1707
|
-
throw new Error('User is not authenticated.')
|
|
1708
|
-
}
|
|
1709
|
-
if (originator === this.adminOriginator) {
|
|
1710
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1711
|
-
}
|
|
1874
|
+
this.checkAuthAndUnderlying(originator)
|
|
1712
1875
|
return this.underlying!.getNetwork({}, originator)
|
|
1713
1876
|
}
|
|
1714
1877
|
|
|
1715
1878
|
async getVersion(_: {}, originator?: OriginatorDomainNameStringUnder250Bytes): Promise<GetVersionResult> {
|
|
1716
|
-
|
|
1717
|
-
throw new Error('User is not authenticated.')
|
|
1718
|
-
}
|
|
1719
|
-
if (originator === this.adminOriginator) {
|
|
1720
|
-
throw new Error('External applications are not allowed to use the admin originator.')
|
|
1721
|
-
}
|
|
1879
|
+
this.checkAuthAndUnderlying(originator)
|
|
1722
1880
|
return this.underlying!.getVersion({}, originator)
|
|
1723
1881
|
}
|
|
1724
1882
|
}
|