@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
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CWIStyleWalletManager = exports.OverlayUMPTokenInteractor = exports.PBKDF2_NUM_ROUNDS = void 0;
|
|
3
|
+
exports.CWIStyleWalletManager = exports.OverlayUMPTokenInteractor = exports.DEFAULT_PROFILE_ID = exports.PBKDF2_NUM_ROUNDS = void 0;
|
|
4
4
|
const sdk_1 = require("@bsv/sdk");
|
|
5
5
|
const PrivilegedKeyManager_1 = require("./sdk/PrivilegedKeyManager");
|
|
6
6
|
/**
|
|
7
7
|
* Number of rounds used in PBKDF2 for deriving password keys.
|
|
8
8
|
*/
|
|
9
9
|
exports.PBKDF2_NUM_ROUNDS = 7777;
|
|
10
|
+
/**
|
|
11
|
+
* Unique Identifier for the default profile (16 zero bytes).
|
|
12
|
+
*/
|
|
13
|
+
exports.DEFAULT_PROFILE_ID = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
10
14
|
/**
|
|
11
15
|
* @class OverlayUMPTokenInteractor
|
|
12
16
|
*
|
|
@@ -67,28 +71,16 @@ class OverlayUMPTokenInteractor {
|
|
|
67
71
|
* then broadcast and published under the `tm_users` topic using a SHIP broadcast, ensuring
|
|
68
72
|
* overlay participants see the updated token.
|
|
69
73
|
*
|
|
70
|
-
* @param wallet The wallet used to build and sign the transaction.
|
|
74
|
+
* @param wallet The wallet used to build and sign the transaction (MUST be operating under the DEFAULT profile).
|
|
71
75
|
* @param adminOriginator The domain/FQDN of the administrative originator (wallet operator).
|
|
72
76
|
* @param token The new UMPToken to create on-chain.
|
|
73
77
|
* @param oldTokenToConsume Optionally, an existing token to consume/spend in the same transaction.
|
|
74
78
|
* @returns The outpoint of the newly created UMP token (e.g. "abcd1234...ef.0").
|
|
75
79
|
*/
|
|
76
|
-
async buildAndSend(wallet,
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
const fields =
|
|
80
|
-
// See: UMP field ordering
|
|
81
|
-
// 0 => passwordSalt
|
|
82
|
-
// 1 => passwordPresentationPrimary
|
|
83
|
-
// 2 => passwordRecoveryPrimary
|
|
84
|
-
// 3 => presentationRecoveryPrimary
|
|
85
|
-
// 4 => passwordPrimaryPrivileged
|
|
86
|
-
// 5 => presentationRecoveryPrivileged
|
|
87
|
-
// 6 => presentationHash
|
|
88
|
-
// 7 => recoveryHash
|
|
89
|
-
// 8 => presentationKeyEncrypted
|
|
90
|
-
// 9 => passwordKeyEncrypted
|
|
91
|
-
// 10 => recoveryKeyEncrypted
|
|
80
|
+
async buildAndSend(wallet, // This wallet MUST be the one built for the default profile
|
|
81
|
+
adminOriginator, token, oldTokenToConsume) {
|
|
82
|
+
// 1) Construct the data fields for the new UMP token.
|
|
83
|
+
const fields = [];
|
|
92
84
|
fields[0] = token.passwordSalt;
|
|
93
85
|
fields[1] = token.passwordPresentationPrimary;
|
|
94
86
|
fields[2] = token.passwordRecoveryPrimary;
|
|
@@ -100,13 +92,17 @@ class OverlayUMPTokenInteractor {
|
|
|
100
92
|
fields[8] = token.presentationKeyEncrypted;
|
|
101
93
|
fields[9] = token.passwordKeyEncrypted;
|
|
102
94
|
fields[10] = token.recoveryKeyEncrypted;
|
|
103
|
-
//
|
|
95
|
+
// Optional field (11) for encrypted profiles
|
|
96
|
+
if (token.profilesEncrypted) {
|
|
97
|
+
fields[11] = token.profilesEncrypted;
|
|
98
|
+
}
|
|
99
|
+
// 2) Create a PushDrop script referencing these fields, locked with the admin key.
|
|
104
100
|
const script = await new sdk_1.PushDrop(wallet, adminOriginator).lock(fields, [2, 'admin user management token'], // protocolID
|
|
105
101
|
'1', // keyID
|
|
106
102
|
'self', // counterparty
|
|
107
103
|
/*forSelf=*/ true,
|
|
108
104
|
/*includeSignature=*/ true);
|
|
109
|
-
// 3) Prepare the createAction call. If oldTokenToConsume is provided,
|
|
105
|
+
// 3) Prepare the createAction call. If oldTokenToConsume is provided, gather the outpoint.
|
|
110
106
|
const inputs = [];
|
|
111
107
|
let inputToken;
|
|
112
108
|
if (oldTokenToConsume === null || oldTokenToConsume === void 0 ? void 0 : oldTokenToConsume.currentOutpoint) {
|
|
@@ -131,8 +127,7 @@ class OverlayUMPTokenInteractor {
|
|
|
131
127
|
outputs,
|
|
132
128
|
inputBEEF: inputToken === null || inputToken === void 0 ? void 0 : inputToken.beef
|
|
133
129
|
}, adminOriginator);
|
|
134
|
-
// If the transaction is fully processed by the wallet
|
|
135
|
-
// we retrieve the final TXID from the result.
|
|
130
|
+
// If the transaction is fully processed by the wallet
|
|
136
131
|
if (!createResult.signableTransaction) {
|
|
137
132
|
const finalTxid = createResult.txid || (createResult.tx ? sdk_1.Transaction.fromAtomicBEEF(createResult.tx).id('hex') : undefined);
|
|
138
133
|
if (!finalTxid) {
|
|
@@ -168,19 +163,25 @@ class OverlayUMPTokenInteractor {
|
|
|
168
163
|
}
|
|
169
164
|
// 6) Broadcast to `tm_users`
|
|
170
165
|
const finalAtomicTx = signResult.tx;
|
|
166
|
+
if (!finalAtomicTx) {
|
|
167
|
+
throw new Error('Final transaction data missing after signing renewed UMP token.');
|
|
168
|
+
}
|
|
171
169
|
const broadcastTx = sdk_1.Transaction.fromAtomicBEEF(finalAtomicTx);
|
|
172
170
|
const result = await this.broadcaster.broadcast(broadcastTx);
|
|
173
171
|
console.log('BROADCAST RESULT', result);
|
|
174
172
|
return `${finalTxid}.0`;
|
|
175
173
|
}
|
|
176
174
|
else {
|
|
177
|
-
//
|
|
175
|
+
// Fallback for creating a new token (no input spending)
|
|
178
176
|
const signResult = await wallet.signAction({ reference, spends: {} }, adminOriginator);
|
|
179
177
|
finalTxid = signResult.txid || (signResult.tx ? sdk_1.Transaction.fromAtomicBEEF(signResult.tx).id('hex') : '');
|
|
180
178
|
if (!finalTxid) {
|
|
181
179
|
throw new Error('Failed to finalize new UMP token transaction.');
|
|
182
180
|
}
|
|
183
181
|
const finalAtomicTx = signResult.tx;
|
|
182
|
+
if (!finalAtomicTx) {
|
|
183
|
+
throw new Error('Final transaction data missing after signing new UMP token.');
|
|
184
|
+
}
|
|
184
185
|
const broadcastTx = sdk_1.Transaction.fromAtomicBEEF(finalAtomicTx);
|
|
185
186
|
const result = await this.broadcaster.broadcast(broadcastTx);
|
|
186
187
|
console.log('BROADCAST RESULT', result);
|
|
@@ -196,24 +197,26 @@ class OverlayUMPTokenInteractor {
|
|
|
196
197
|
* @returns The parsed UMPToken or `undefined` if none found/decodable.
|
|
197
198
|
*/
|
|
198
199
|
parseLookupAnswer(answer) {
|
|
200
|
+
var _a;
|
|
199
201
|
if (answer.type !== 'output-list') {
|
|
200
202
|
return undefined;
|
|
201
203
|
}
|
|
202
204
|
if (!answer.outputs || answer.outputs.length === 0) {
|
|
203
205
|
return undefined;
|
|
204
206
|
}
|
|
205
|
-
// We expect only one relevant UMP token in most queries, so let's parse the first.
|
|
206
|
-
// If multiple are returned, we can parse the first.
|
|
207
207
|
const { beef, outputIndex } = answer.outputs[0];
|
|
208
208
|
try {
|
|
209
209
|
const tx = sdk_1.Transaction.fromBEEF(beef);
|
|
210
210
|
const outpoint = `${tx.id('hex')}.${outputIndex}`;
|
|
211
211
|
const decoded = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
|
|
212
|
-
// Expecting 11 fields for UMP
|
|
213
|
-
if (!decoded.fields || decoded.fields.length < 11)
|
|
212
|
+
// Expecting 11 or more fields for UMP
|
|
213
|
+
if (!decoded.fields || decoded.fields.length < 11) {
|
|
214
|
+
console.warn(`Unexpected number of fields in UMP token: ${(_a = decoded.fields) === null || _a === void 0 ? void 0 : _a.length}`);
|
|
214
215
|
return undefined;
|
|
216
|
+
}
|
|
215
217
|
// Build the UMP token from these fields, preserving outpoint
|
|
216
218
|
const t = {
|
|
219
|
+
// Order matches buildAndSend and serialize/deserialize
|
|
217
220
|
passwordSalt: decoded.fields[0],
|
|
218
221
|
passwordPresentationPrimary: decoded.fields[1],
|
|
219
222
|
passwordRecoveryPrimary: decoded.fields[2],
|
|
@@ -225,12 +228,13 @@ class OverlayUMPTokenInteractor {
|
|
|
225
228
|
presentationKeyEncrypted: decoded.fields[8],
|
|
226
229
|
passwordKeyEncrypted: decoded.fields[9],
|
|
227
230
|
recoveryKeyEncrypted: decoded.fields[10],
|
|
231
|
+
profilesEncrypted: decoded.fields[12] ? decoded.fields[11] : undefined, // If there's a signature in field 12, use field 11
|
|
228
232
|
currentOutpoint: outpoint
|
|
229
233
|
};
|
|
230
234
|
return t;
|
|
231
235
|
}
|
|
232
236
|
catch (e) {
|
|
233
|
-
|
|
237
|
+
console.error('Failed to parse or decode UMP token:', e);
|
|
234
238
|
return undefined;
|
|
235
239
|
}
|
|
236
240
|
}
|
|
@@ -249,7 +253,7 @@ class OverlayUMPTokenInteractor {
|
|
|
249
253
|
if (results.type !== 'output-list') {
|
|
250
254
|
return undefined;
|
|
251
255
|
}
|
|
252
|
-
if (!results.outputs.length) {
|
|
256
|
+
if (!results.outputs || !results.outputs.length) {
|
|
253
257
|
return undefined;
|
|
254
258
|
}
|
|
255
259
|
return results.outputs[0];
|
|
@@ -258,35 +262,38 @@ class OverlayUMPTokenInteractor {
|
|
|
258
262
|
exports.OverlayUMPTokenInteractor = OverlayUMPTokenInteractor;
|
|
259
263
|
/**
|
|
260
264
|
* Manages a "CWI-style" wallet that uses a UMP token and a
|
|
261
|
-
* multi-key authentication scheme (password, presentation key, and recovery key)
|
|
265
|
+
* multi-key authentication scheme (password, presentation key, and recovery key),
|
|
266
|
+
* supporting multiple user profiles under a single account.
|
|
262
267
|
*/
|
|
263
268
|
class CWIStyleWalletManager {
|
|
264
269
|
/**
|
|
265
270
|
* Constructs a new CWIStyleWalletManager.
|
|
266
271
|
*
|
|
267
272
|
* @param adminOriginator The domain name of the administrative originator.
|
|
268
|
-
* @param walletBuilder A function that can build an underlying wallet instance
|
|
269
|
-
*
|
|
270
|
-
* @param
|
|
271
|
-
* @param
|
|
272
|
-
* @param
|
|
273
|
-
* @param
|
|
274
|
-
* @param stateSnapshot If provided, a previously saved snapshot of the wallet's state.
|
|
273
|
+
* @param walletBuilder A function that can build an underlying wallet instance for a profile.
|
|
274
|
+
* @param interactor An instance of UMPTokenInteractor.
|
|
275
|
+
* @param recoveryKeySaver A function to persist a new recovery key.
|
|
276
|
+
* @param passwordRetriever A function to request the user's password.
|
|
277
|
+
* @param newWalletFunder Optional function to fund a new wallet.
|
|
278
|
+
* @param stateSnapshot Optional previously saved state snapshot.
|
|
275
279
|
*/
|
|
276
280
|
constructor(adminOriginator, walletBuilder, interactor = new OverlayUMPTokenInteractor(), recoveryKeySaver, passwordRetriever, newWalletFunder, stateSnapshot) {
|
|
277
281
|
/**
|
|
278
|
-
*
|
|
279
|
-
* - 'presentation-key-and-password'
|
|
280
|
-
* - 'presentation-key-and-recovery-key'
|
|
281
|
-
* - 'recovery-key-and-password'
|
|
282
|
+
* Current mode of authentication.
|
|
282
283
|
*/
|
|
283
284
|
this.authenticationMode = 'presentation-key-and-password';
|
|
284
285
|
/**
|
|
285
|
-
* Indicates
|
|
286
|
-
* - 'new-user'
|
|
287
|
-
* - 'existing-user'
|
|
286
|
+
* Indicates new user or existing user flow.
|
|
288
287
|
*/
|
|
289
288
|
this.authenticationFlow = 'new-user';
|
|
289
|
+
/**
|
|
290
|
+
* The currently active profile ID (null or DEFAULT_PROFILE_ID means default profile).
|
|
291
|
+
*/
|
|
292
|
+
this.activeProfileId = exports.DEFAULT_PROFILE_ID;
|
|
293
|
+
/**
|
|
294
|
+
* List of loaded non-default profiles.
|
|
295
|
+
*/
|
|
296
|
+
this.profiles = [];
|
|
290
297
|
this.adminOriginator = adminOriginator;
|
|
291
298
|
this.walletBuilder = walletBuilder;
|
|
292
299
|
this.UMPTokenInteractor = interactor;
|
|
@@ -295,17 +302,20 @@ class CWIStyleWalletManager {
|
|
|
295
302
|
this.authenticated = false;
|
|
296
303
|
this.newWalletFunder = newWalletFunder;
|
|
297
304
|
// If a saved snapshot is provided, attempt to load it.
|
|
305
|
+
// Note: loadSnapshot now returns a promise. We don't await it here,
|
|
306
|
+
// as the constructor must be synchronous. The caller should check
|
|
307
|
+
// `this.authenticated` after construction if a snapshot was provided.
|
|
298
308
|
if (stateSnapshot) {
|
|
299
|
-
this.loadSnapshot(stateSnapshot)
|
|
309
|
+
this.loadSnapshot(stateSnapshot).catch(err => {
|
|
310
|
+
console.error('Failed to load snapshot during construction:', err);
|
|
311
|
+
// Clear potentially partially loaded state
|
|
312
|
+
this.destroy();
|
|
313
|
+
});
|
|
300
314
|
}
|
|
301
315
|
}
|
|
316
|
+
// --- Authentication Methods ---
|
|
302
317
|
/**
|
|
303
|
-
* Provides the presentation key
|
|
304
|
-
* If a UMP token is found based on the key's hash, this is an existing-user flow.
|
|
305
|
-
* Otherwise, it is treated as a new-user flow.
|
|
306
|
-
*
|
|
307
|
-
* @param key The user's presentation key (32 bytes).
|
|
308
|
-
* @throws {Error} if user is already authenticated, or if the current mode does not require a presentation key.
|
|
318
|
+
* Provides the presentation key.
|
|
309
319
|
*/
|
|
310
320
|
async providePresentationKey(key) {
|
|
311
321
|
if (this.authenticated) {
|
|
@@ -329,17 +339,7 @@ class CWIStyleWalletManager {
|
|
|
329
339
|
}
|
|
330
340
|
}
|
|
331
341
|
/**
|
|
332
|
-
* Provides the password
|
|
333
|
-
*
|
|
334
|
-
* - **Existing user**:
|
|
335
|
-
* Decrypts the primary key using the provided password (and either the presentation key or recovery key, depending on the mode).
|
|
336
|
-
* Then builds the underlying wallet, marking the user as authenticated.
|
|
337
|
-
*
|
|
338
|
-
* - **New user**:
|
|
339
|
-
* Generates a new UMP token with fresh keys (primary, privileged, recovery). Publishes it on-chain and builds the wallet.
|
|
340
|
-
*
|
|
341
|
-
* @param password The user's password as a string.
|
|
342
|
-
* @throws {Error} If the user is already authenticated, if the mode does not use a password, or if required keys are missing.
|
|
342
|
+
* Provides the password.
|
|
343
343
|
*/
|
|
344
344
|
async providePassword(password) {
|
|
345
345
|
if (this.authenticated) {
|
|
@@ -348,113 +348,112 @@ class CWIStyleWalletManager {
|
|
|
348
348
|
if (this.authenticationMode === 'presentation-key-and-recovery-key') {
|
|
349
349
|
throw new Error('Password is not needed in this mode');
|
|
350
350
|
}
|
|
351
|
-
// If we detect an existing user flow:
|
|
352
351
|
if (this.authenticationFlow === 'existing-user') {
|
|
352
|
+
// Existing user flow
|
|
353
353
|
if (!this.currentUMPToken) {
|
|
354
|
-
throw new Error('Provide
|
|
354
|
+
throw new Error('Provide presentation or recovery key first.');
|
|
355
355
|
}
|
|
356
356
|
const derivedPasswordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(password, 'utf8'), this.currentUMPToken.passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
|
|
357
|
+
let rootPrimaryKey;
|
|
358
|
+
let rootPrivilegedKey; // Only needed for recovery mode
|
|
357
359
|
if (this.authenticationMode === 'presentation-key-and-password') {
|
|
358
|
-
if (!this.presentationKey)
|
|
360
|
+
if (!this.presentationKey)
|
|
359
361
|
throw new Error('No presentation key found!');
|
|
360
|
-
}
|
|
361
|
-
// Decrypt the primary key with XOR(presentationKey, derivedPasswordKey).
|
|
362
362
|
const xorKey = this.XOR(this.presentationKey, derivedPasswordKey);
|
|
363
|
-
|
|
364
|
-
await this.buildUnderlying(decryptedPrimary);
|
|
363
|
+
rootPrimaryKey = new sdk_1.SymmetricKey(xorKey).decrypt(this.currentUMPToken.passwordPresentationPrimary);
|
|
365
364
|
}
|
|
366
365
|
else {
|
|
367
|
-
// 'recovery-key-and-password'
|
|
368
|
-
if (!this.recoveryKey)
|
|
366
|
+
// 'recovery-key-and-password'
|
|
367
|
+
if (!this.recoveryKey)
|
|
369
368
|
throw new Error('No recovery key found!');
|
|
370
|
-
}
|
|
371
|
-
// Decrypt the primary key with XOR(recoveryKey, derivedPasswordKey).
|
|
372
369
|
const primaryDecryptionKey = this.XOR(this.recoveryKey, derivedPasswordKey);
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const decryptedPrivileged = new sdk_1.SymmetricKey(privilegedDecryptionKey).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
|
|
377
|
-
await this.buildUnderlying(decryptedPrimary, decryptedPrivileged);
|
|
370
|
+
rootPrimaryKey = new sdk_1.SymmetricKey(primaryDecryptionKey).decrypt(this.currentUMPToken.passwordRecoveryPrimary);
|
|
371
|
+
const privilegedDecryptionKey = this.XOR(rootPrimaryKey, derivedPasswordKey);
|
|
372
|
+
rootPrivilegedKey = new sdk_1.SymmetricKey(privilegedDecryptionKey).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
|
|
378
373
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (this.authenticationMode !== 'presentation-key-and-password') {
|
|
383
|
-
throw new Error('New-user flow requires presentation key and password, not recovery key mode.');
|
|
384
|
-
}
|
|
385
|
-
if (!this.presentationKey) {
|
|
386
|
-
throw new Error('No presentation key provided for new-user flow.');
|
|
374
|
+
// Build root infrastructure, load profiles, and switch to default profile initially
|
|
375
|
+
await this.setupRootInfrastructure(rootPrimaryKey, rootPrivilegedKey);
|
|
376
|
+
await this.switchProfile(this.activeProfileId);
|
|
387
377
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
// Build XOR-based symmetrical keys:
|
|
396
|
-
const presentationPassword = new sdk_1.SymmetricKey(this.XOR(this.presentationKey, passwordKey));
|
|
397
|
-
const presentationRecovery = new sdk_1.SymmetricKey(this.XOR(this.presentationKey, recoveryKey));
|
|
398
|
-
const recoveryPassword = new sdk_1.SymmetricKey(this.XOR(recoveryKey, passwordKey));
|
|
399
|
-
const primaryPassword = new sdk_1.SymmetricKey(this.XOR(primaryKey, passwordKey));
|
|
400
|
-
// Temporarily create a privileged key manager for encrypting the keys in the token.
|
|
401
|
-
const tempPrivilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async () => new sdk_1.PrivateKey(privilegedKey));
|
|
402
|
-
// Build the new UMP token:
|
|
403
|
-
const newToken = {
|
|
404
|
-
passwordSalt,
|
|
405
|
-
passwordPresentationPrimary: presentationPassword.encrypt(primaryKey),
|
|
406
|
-
passwordRecoveryPrimary: recoveryPassword.encrypt(primaryKey),
|
|
407
|
-
presentationRecoveryPrimary: presentationRecovery.encrypt(primaryKey),
|
|
408
|
-
passwordPrimaryPrivileged: primaryPassword.encrypt(privilegedKey),
|
|
409
|
-
presentationRecoveryPrivileged: presentationRecovery.encrypt(privilegedKey),
|
|
410
|
-
presentationHash: sdk_1.Hash.sha256(this.presentationKey),
|
|
411
|
-
recoveryHash: sdk_1.Hash.sha256(recoveryKey),
|
|
412
|
-
presentationKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
|
|
413
|
-
plaintext: this.presentationKey,
|
|
414
|
-
protocolID: [2, 'admin key wrapping'],
|
|
415
|
-
keyID: '1'
|
|
416
|
-
})).ciphertext,
|
|
417
|
-
passwordKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
|
|
418
|
-
plaintext: passwordKey,
|
|
419
|
-
protocolID: [2, 'admin key wrapping'],
|
|
420
|
-
keyID: '1'
|
|
421
|
-
})).ciphertext,
|
|
422
|
-
recoveryKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
|
|
423
|
-
plaintext: recoveryKey,
|
|
424
|
-
protocolID: [2, 'admin key wrapping'],
|
|
425
|
-
keyID: '1'
|
|
426
|
-
})).ciphertext
|
|
427
|
-
};
|
|
428
|
-
// Now, we can create our new wallet!
|
|
429
|
-
this.currentUMPToken = newToken;
|
|
430
|
-
await this.buildUnderlying(primaryKey);
|
|
431
|
-
// Before we do anything, the new wallet is most likely empty right now.
|
|
432
|
-
// We want to provide a chance for someone to fund it, if they want.
|
|
433
|
-
if (this.newWalletFunder) {
|
|
434
|
-
try {
|
|
435
|
-
await this.newWalletFunder(this.presentationKey, this.underlying, this.adminOriginator);
|
|
378
|
+
else {
|
|
379
|
+
// New user flow (only 'presentation-key-and-password')
|
|
380
|
+
if (this.authenticationMode !== 'presentation-key-and-password') {
|
|
381
|
+
throw new Error('New-user flow requires presentation key and password mode.');
|
|
382
|
+
}
|
|
383
|
+
if (!this.presentationKey) {
|
|
384
|
+
throw new Error('No presentation key provided for new-user flow.');
|
|
436
385
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
386
|
+
// Generate new keys/salt
|
|
387
|
+
const recoveryKey = (0, sdk_1.Random)(32);
|
|
388
|
+
await this.recoveryKeySaver(recoveryKey);
|
|
389
|
+
const passwordSalt = (0, sdk_1.Random)(32);
|
|
390
|
+
const passwordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(password, 'utf8'), passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
|
|
391
|
+
const rootPrimaryKey = (0, sdk_1.Random)(32);
|
|
392
|
+
const rootPrivilegedKey = (0, sdk_1.Random)(32);
|
|
393
|
+
// Build XOR keys
|
|
394
|
+
const presentationPassword = new sdk_1.SymmetricKey(this.XOR(this.presentationKey, passwordKey));
|
|
395
|
+
const presentationRecovery = new sdk_1.SymmetricKey(this.XOR(this.presentationKey, recoveryKey));
|
|
396
|
+
const recoveryPassword = new sdk_1.SymmetricKey(this.XOR(recoveryKey, passwordKey));
|
|
397
|
+
const primaryPassword = new sdk_1.SymmetricKey(this.XOR(rootPrimaryKey, passwordKey));
|
|
398
|
+
// Temp manager for encryption
|
|
399
|
+
const tempPrivilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async () => new sdk_1.PrivateKey(rootPrivilegedKey));
|
|
400
|
+
// Build new UMP token (no profiles initially)
|
|
401
|
+
const newToken = {
|
|
402
|
+
passwordSalt,
|
|
403
|
+
passwordPresentationPrimary: presentationPassword.encrypt(rootPrimaryKey),
|
|
404
|
+
passwordRecoveryPrimary: recoveryPassword.encrypt(rootPrimaryKey),
|
|
405
|
+
presentationRecoveryPrimary: presentationRecovery.encrypt(rootPrimaryKey),
|
|
406
|
+
passwordPrimaryPrivileged: primaryPassword.encrypt(rootPrivilegedKey),
|
|
407
|
+
presentationRecoveryPrivileged: presentationRecovery.encrypt(rootPrivilegedKey),
|
|
408
|
+
presentationHash: sdk_1.Hash.sha256(this.presentationKey),
|
|
409
|
+
recoveryHash: sdk_1.Hash.sha256(recoveryKey),
|
|
410
|
+
presentationKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
|
|
411
|
+
plaintext: this.presentationKey,
|
|
412
|
+
protocolID: [2, 'admin key wrapping'],
|
|
413
|
+
keyID: '1'
|
|
414
|
+
})).ciphertext,
|
|
415
|
+
passwordKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
|
|
416
|
+
plaintext: passwordKey,
|
|
417
|
+
protocolID: [2, 'admin key wrapping'],
|
|
418
|
+
keyID: '1'
|
|
419
|
+
})).ciphertext,
|
|
420
|
+
recoveryKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
|
|
421
|
+
plaintext: recoveryKey,
|
|
422
|
+
protocolID: [2, 'admin key wrapping'],
|
|
423
|
+
keyID: '1'
|
|
424
|
+
})).ciphertext,
|
|
425
|
+
profilesEncrypted: undefined // No profiles yet
|
|
426
|
+
};
|
|
427
|
+
this.currentUMPToken = newToken;
|
|
428
|
+
// Setup root infrastructure and switch to default profile
|
|
429
|
+
await this.setupRootInfrastructure(rootPrimaryKey);
|
|
430
|
+
await this.switchProfile(exports.DEFAULT_PROFILE_ID);
|
|
431
|
+
// Fund the *default* wallet if funder provided
|
|
432
|
+
if (this.newWalletFunder && this.underlying) {
|
|
433
|
+
try {
|
|
434
|
+
await this.newWalletFunder(this.presentationKey, this.underlying, this.adminOriginator);
|
|
435
|
+
}
|
|
436
|
+
catch (e) {
|
|
437
|
+
console.error('Error funding new wallet:', e);
|
|
438
|
+
// Decide if this should halt the process or just log
|
|
439
|
+
}
|
|
441
440
|
}
|
|
441
|
+
// Publish the new UMP token *after* potentially funding
|
|
442
|
+
// We need the default profile wallet to sign the UMP creation TX
|
|
443
|
+
if (!this.underlying) {
|
|
444
|
+
throw new Error('Default profile wallet not built before attempting to publish UMP token.');
|
|
445
|
+
}
|
|
446
|
+
this.currentUMPToken.currentOutpoint = await this.UMPTokenInteractor.buildAndSend(this.underlying, // Use the default profile wallet
|
|
447
|
+
this.adminOriginator, newToken);
|
|
442
448
|
}
|
|
443
|
-
// Publish the new UMP token on-chain and store the resulting outpoint.
|
|
444
|
-
this.currentUMPToken.currentOutpoint = await this.UMPTokenInteractor.buildAndSend(this.underlying, this.adminOriginator, newToken);
|
|
445
449
|
}
|
|
446
450
|
/**
|
|
447
|
-
* Provides the recovery key
|
|
448
|
-
*
|
|
449
|
-
* @param recoveryKey The user's recovery key (32 bytes).
|
|
450
|
-
* @throws {Error} if user is already authenticated, if the mode does not use a recovery key,
|
|
451
|
-
* or if a required presentation key is missing in "presentation-key-and-recovery-key" mode.
|
|
451
|
+
* Provides the recovery key.
|
|
452
452
|
*/
|
|
453
453
|
async provideRecoveryKey(recoveryKey) {
|
|
454
454
|
if (this.authenticated) {
|
|
455
455
|
throw new Error('Already authenticated');
|
|
456
456
|
}
|
|
457
|
-
// Cannot use recovery key in a new-user flow
|
|
458
457
|
if (this.authenticationFlow === 'new-user') {
|
|
459
458
|
throw new Error('Do not submit recovery key in new-user flow');
|
|
460
459
|
}
|
|
@@ -462,623 +461,743 @@ class CWIStyleWalletManager {
|
|
|
462
461
|
throw new Error('No recovery key required in this mode');
|
|
463
462
|
}
|
|
464
463
|
else if (this.authenticationMode === 'recovery-key-and-password') {
|
|
465
|
-
//
|
|
464
|
+
// Wait for password
|
|
466
465
|
const hash = sdk_1.Hash.sha256(recoveryKey);
|
|
467
466
|
const token = await this.UMPTokenInteractor.findByRecoveryKeyHash(hash);
|
|
468
|
-
if (!token)
|
|
469
|
-
throw new Error('No user found with this key');
|
|
470
|
-
}
|
|
467
|
+
if (!token)
|
|
468
|
+
throw new Error('No user found with this recovery key');
|
|
471
469
|
this.recoveryKey = recoveryKey;
|
|
472
470
|
this.currentUMPToken = token;
|
|
473
471
|
}
|
|
474
472
|
else {
|
|
475
473
|
// 'presentation-key-and-recovery-key'
|
|
476
|
-
if (!this.presentationKey)
|
|
474
|
+
if (!this.presentationKey)
|
|
477
475
|
throw new Error('Provide the presentation key first');
|
|
478
|
-
|
|
479
|
-
if (!this.currentUMPToken) {
|
|
476
|
+
if (!this.currentUMPToken)
|
|
480
477
|
throw new Error('Current UMP token not found');
|
|
481
|
-
}
|
|
482
|
-
// Decrypt the primary key:
|
|
483
478
|
const xorKey = this.XOR(this.presentationKey, recoveryKey);
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
await this.
|
|
479
|
+
const rootPrimaryKey = new sdk_1.SymmetricKey(xorKey).decrypt(this.currentUMPToken.presentationRecoveryPrimary);
|
|
480
|
+
const rootPrivilegedKey = new sdk_1.SymmetricKey(xorKey).decrypt(this.currentUMPToken.presentationRecoveryPrivileged);
|
|
481
|
+
// Build root infrastructure, load profiles, switch to default
|
|
482
|
+
await this.setupRootInfrastructure(rootPrimaryKey, rootPrivilegedKey);
|
|
483
|
+
await this.switchProfile(this.activeProfileId);
|
|
488
484
|
}
|
|
489
485
|
}
|
|
486
|
+
// --- State Management Methods ---
|
|
490
487
|
/**
|
|
491
|
-
* Saves the current wallet state (
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
* @remarks
|
|
496
|
-
* Storing the snapshot provides a fully authenticated state.
|
|
497
|
-
* This **must** be securely stored (e.g. system keychain or encrypted file).
|
|
498
|
-
* If attackers gain access to this snapshot, they can fully control the wallet.
|
|
488
|
+
* Saves the current wallet state (root key, UMP token, active profile) into an encrypted snapshot.
|
|
489
|
+
* Version 2 format: [1 byte version=2] + [32 byte snapshot key] + [16 byte activeProfileId] + [encrypted payload]
|
|
490
|
+
* Encrypted Payload: [32 byte rootPrimaryKey] + [varint token length + serialized UMP token]
|
|
499
491
|
*
|
|
500
|
-
* @returns
|
|
501
|
-
* @throws {Error} if no primary key or token is currently set.
|
|
492
|
+
* @returns Encrypted snapshot bytes.
|
|
502
493
|
*/
|
|
503
494
|
saveSnapshot() {
|
|
504
|
-
if (!this.
|
|
505
|
-
throw new Error('No primary key or current UMP token set');
|
|
495
|
+
if (!this.rootPrimaryKey || !this.currentUMPToken) {
|
|
496
|
+
throw new Error('No root primary key or current UMP token set');
|
|
506
497
|
}
|
|
507
|
-
// Generate a random snapshot encryption key:
|
|
508
498
|
const snapshotKey = (0, sdk_1.Random)(32);
|
|
509
|
-
// Serialize the relevant data to a preimage buffer:
|
|
510
499
|
const snapshotPreimageWriter = new sdk_1.Utils.Writer();
|
|
511
|
-
// Write
|
|
512
|
-
snapshotPreimageWriter.write(this.
|
|
513
|
-
// Write
|
|
500
|
+
// Write root primary key
|
|
501
|
+
snapshotPreimageWriter.write(this.rootPrimaryKey);
|
|
502
|
+
// Write serialized UMP token (must have outpoint)
|
|
503
|
+
if (!this.currentUMPToken.currentOutpoint) {
|
|
504
|
+
throw new Error('UMP token cannot be saved without a current outpoint.');
|
|
505
|
+
}
|
|
514
506
|
const serializedToken = this.serializeUMPToken(this.currentUMPToken);
|
|
507
|
+
snapshotPreimageWriter.writeVarIntNum(serializedToken.length);
|
|
515
508
|
snapshotPreimageWriter.write(serializedToken);
|
|
516
|
-
// Encrypt the
|
|
509
|
+
// Encrypt the payload
|
|
517
510
|
const snapshotPreimage = snapshotPreimageWriter.toArray();
|
|
518
511
|
const snapshotPayload = new sdk_1.SymmetricKey(snapshotKey).encrypt(snapshotPreimage);
|
|
519
|
-
// Build
|
|
512
|
+
// Build final snapshot (Version 2)
|
|
520
513
|
const snapshotWriter = new sdk_1.Utils.Writer();
|
|
514
|
+
snapshotWriter.writeUInt8(2); // Version
|
|
521
515
|
snapshotWriter.write(snapshotKey);
|
|
522
|
-
snapshotWriter.write(
|
|
516
|
+
snapshotWriter.write(this.activeProfileId); // Active profile ID
|
|
517
|
+
snapshotWriter.write(snapshotPayload); // Encrypted data
|
|
523
518
|
return snapshotWriter.toArray();
|
|
524
519
|
}
|
|
525
520
|
/**
|
|
526
|
-
* Loads a previously saved state snapshot
|
|
527
|
-
*
|
|
521
|
+
* Loads a previously saved state snapshot. Restores root key, UMP token, profiles, and active profile.
|
|
522
|
+
* Handles Version 1 (legacy) and Version 2 formats.
|
|
528
523
|
*
|
|
529
|
-
* @param snapshot
|
|
530
|
-
* @throws {Error} If the snapshot format is invalid or decryption fails.
|
|
524
|
+
* @param snapshot Encrypted snapshot bytes.
|
|
531
525
|
*/
|
|
532
526
|
async loadSnapshot(snapshot) {
|
|
533
527
|
try {
|
|
534
528
|
const reader = new sdk_1.Utils.Reader(snapshot);
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
529
|
+
const version = reader.readUInt8();
|
|
530
|
+
let snapshotKey;
|
|
531
|
+
let encryptedPayload;
|
|
532
|
+
let activeProfileId = exports.DEFAULT_PROFILE_ID; // Default for V1
|
|
533
|
+
if (version === 1) {
|
|
534
|
+
snapshotKey = reader.read(32);
|
|
535
|
+
encryptedPayload = reader.read();
|
|
536
|
+
}
|
|
537
|
+
else if (version === 2) {
|
|
538
|
+
snapshotKey = reader.read(32);
|
|
539
|
+
activeProfileId = reader.read(16); // Read active profile ID
|
|
540
|
+
encryptedPayload = reader.read();
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
throw new Error(`Unsupported snapshot version: ${version}`);
|
|
544
|
+
}
|
|
545
|
+
// Decrypt payload
|
|
540
546
|
const decryptedPayload = new sdk_1.SymmetricKey(snapshotKey).decrypt(encryptedPayload);
|
|
541
547
|
const payloadReader = new sdk_1.Utils.Reader(decryptedPayload);
|
|
542
|
-
// Read
|
|
543
|
-
const
|
|
544
|
-
// Read
|
|
545
|
-
const
|
|
548
|
+
// Read root primary key
|
|
549
|
+
const rootPrimaryKey = payloadReader.read(32);
|
|
550
|
+
// Read serialized UMP token
|
|
551
|
+
const tokenLen = payloadReader.readVarIntNum();
|
|
552
|
+
const tokenBytes = payloadReader.read(tokenLen);
|
|
546
553
|
const token = this.deserializeUMPToken(tokenBytes);
|
|
547
|
-
// Assign
|
|
554
|
+
// Assign loaded data
|
|
548
555
|
this.currentUMPToken = token;
|
|
549
|
-
|
|
556
|
+
// Setup root infrastructure, load profiles, and switch to the loaded active profile
|
|
557
|
+
await this.setupRootInfrastructure(rootPrimaryKey); // Will automatically load profiles
|
|
558
|
+
await this.switchProfile(activeProfileId); // Switch to the profile saved in the snapshot
|
|
559
|
+
this.authenticationFlow = 'existing-user'; // Loading implies existing user
|
|
550
560
|
}
|
|
551
561
|
catch (error) {
|
|
562
|
+
this.destroy(); // Clear state on error
|
|
552
563
|
throw new Error(`Failed to load snapshot: ${error.message}`);
|
|
553
564
|
}
|
|
554
565
|
}
|
|
555
566
|
/**
|
|
556
|
-
* Destroys the
|
|
567
|
+
* Destroys the wallet state, clearing keys, tokens, and profiles.
|
|
557
568
|
*/
|
|
558
569
|
destroy() {
|
|
559
570
|
this.underlying = undefined;
|
|
560
|
-
this.
|
|
571
|
+
this.rootPrivilegedKeyManager = undefined;
|
|
561
572
|
this.authenticated = false;
|
|
562
|
-
this.
|
|
573
|
+
this.rootPrimaryKey = undefined;
|
|
563
574
|
this.currentUMPToken = undefined;
|
|
564
575
|
this.presentationKey = undefined;
|
|
565
576
|
this.recoveryKey = undefined;
|
|
577
|
+
this.profiles = [];
|
|
578
|
+
this.activeProfileId = exports.DEFAULT_PROFILE_ID;
|
|
566
579
|
this.authenticationMode = 'presentation-key-and-password';
|
|
567
580
|
this.authenticationFlow = 'new-user';
|
|
568
581
|
}
|
|
582
|
+
// --- Profile Management Methods ---
|
|
569
583
|
/**
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
* @param newPassword The user's new password as a string.
|
|
573
|
-
* @throws {Error} If the user is not authenticated, or if underlying token references are missing.
|
|
584
|
+
* Lists all available profiles, including the default profile.
|
|
585
|
+
* @returns Array of profile info objects, including an 'active' flag.
|
|
574
586
|
*/
|
|
575
|
-
|
|
587
|
+
listProfiles() {
|
|
576
588
|
if (!this.authenticated) {
|
|
577
589
|
throw new Error('Not authenticated.');
|
|
578
590
|
}
|
|
579
|
-
|
|
580
|
-
|
|
591
|
+
const profileList = [
|
|
592
|
+
// Default profile
|
|
593
|
+
{
|
|
594
|
+
id: exports.DEFAULT_PROFILE_ID,
|
|
595
|
+
name: 'default',
|
|
596
|
+
createdAt: null, // Default profile doesn't have a creation timestamp in the same way
|
|
597
|
+
active: this.activeProfileId.every(x => x === 0)
|
|
598
|
+
},
|
|
599
|
+
// Other profiles
|
|
600
|
+
...this.profiles.map(p => ({
|
|
601
|
+
id: p.id,
|
|
602
|
+
name: p.name,
|
|
603
|
+
createdAt: p.createdAt,
|
|
604
|
+
active: this.activeProfileId.every((x, i) => x === p.id[i])
|
|
605
|
+
}))
|
|
606
|
+
];
|
|
607
|
+
return profileList;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Adds a new profile with the given name.
|
|
611
|
+
* Generates necessary pads and updates the UMP token.
|
|
612
|
+
* Does not switch to the new profile automatically.
|
|
613
|
+
*
|
|
614
|
+
* @param name The desired name for the new profile.
|
|
615
|
+
* @returns The ID of the newly created profile.
|
|
616
|
+
*/
|
|
617
|
+
async addProfile(name) {
|
|
618
|
+
if (!this.authenticated || !this.rootPrimaryKey || !this.currentUMPToken || !this.rootPrivilegedKeyManager) {
|
|
619
|
+
throw new Error('Wallet not fully initialized or authenticated.');
|
|
620
|
+
}
|
|
621
|
+
// Ensure name is unique (including 'default')
|
|
622
|
+
if (name === 'default' || this.profiles.some(p => p.name.toLowerCase() === name.toLowerCase())) {
|
|
623
|
+
throw new Error(`Profile name "${name}" is already in use.`);
|
|
624
|
+
}
|
|
625
|
+
const newProfile = {
|
|
626
|
+
name,
|
|
627
|
+
id: (0, sdk_1.Random)(16),
|
|
628
|
+
primaryPad: (0, sdk_1.Random)(32),
|
|
629
|
+
privilegedPad: (0, sdk_1.Random)(32),
|
|
630
|
+
createdAt: Math.floor(Date.now() / 1000)
|
|
631
|
+
};
|
|
632
|
+
this.profiles.push(newProfile);
|
|
633
|
+
// Update the UMP token with the new profile list
|
|
634
|
+
await this.updateAuthFactors(this.currentUMPToken.passwordSalt,
|
|
635
|
+
// Need to re-derive/decrypt factors needed for re-encryption
|
|
636
|
+
await this.getFactor('passwordKey'), await this.getFactor('presentationKey'), await this.getFactor('recoveryKey'), this.rootPrimaryKey, await this.getFactor('privilegedKey', true), // Get ROOT privileged key
|
|
637
|
+
this.profiles // Pass the updated profile list
|
|
638
|
+
);
|
|
639
|
+
return newProfile.id;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Deletes a profile by its ID.
|
|
643
|
+
* Cannot delete the default profile. If the active profile is deleted,
|
|
644
|
+
* it switches back to the default profile.
|
|
645
|
+
*
|
|
646
|
+
* @param profileId The 16-byte ID of the profile to delete.
|
|
647
|
+
*/
|
|
648
|
+
async deleteProfile(profileId) {
|
|
649
|
+
if (!this.authenticated || !this.rootPrimaryKey || !this.currentUMPToken || !this.rootPrivilegedKeyManager) {
|
|
650
|
+
throw new Error('Wallet not fully initialized or authenticated.');
|
|
651
|
+
}
|
|
652
|
+
if (profileId.every(x => x === 0)) {
|
|
653
|
+
throw new Error('Cannot delete the default profile.');
|
|
654
|
+
}
|
|
655
|
+
const profileIndex = this.profiles.findIndex(p => p.id.every((x, i) => x === profileId[i]));
|
|
656
|
+
if (profileIndex === -1) {
|
|
657
|
+
throw new Error('Profile not found.');
|
|
658
|
+
}
|
|
659
|
+
// Remove the profile
|
|
660
|
+
this.profiles.splice(profileIndex, 1);
|
|
661
|
+
// If the deleted profile was active, switch to default
|
|
662
|
+
if (this.activeProfileId.every((x, i) => x === profileId[i])) {
|
|
663
|
+
await this.switchProfile(exports.DEFAULT_PROFILE_ID); // This rebuilds the wallet
|
|
664
|
+
}
|
|
665
|
+
// Update the UMP token
|
|
666
|
+
await this.updateAuthFactors(this.currentUMPToken.passwordSalt, await this.getFactor('passwordKey'), await this.getFactor('presentationKey'), await this.getFactor('recoveryKey'), this.rootPrimaryKey, await this.getFactor('privilegedKey', true), // Get ROOT privileged key
|
|
667
|
+
this.profiles // Pass updated list
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Switches the active profile. This re-derives keys and rebuilds the underlying wallet.
|
|
672
|
+
*
|
|
673
|
+
* @param profileId The 16-byte ID of the profile to switch to (use DEFAULT_PROFILE_ID for default).
|
|
674
|
+
*/
|
|
675
|
+
async switchProfile(profileId) {
|
|
676
|
+
if (!this.authenticated || !this.rootPrimaryKey || !this.rootPrivilegedKeyManager) {
|
|
677
|
+
throw new Error('Cannot switch profile: Wallet not authenticated or root keys missing.');
|
|
678
|
+
}
|
|
679
|
+
let profilePrimaryKey;
|
|
680
|
+
let profilePrivilegedPad; // Pad for the target profile
|
|
681
|
+
if (profileId.every(x => x === 0)) {
|
|
682
|
+
// Switching to default profile
|
|
683
|
+
profilePrimaryKey = this.rootPrimaryKey;
|
|
684
|
+
profilePrivilegedPad = undefined; // No pad for default
|
|
685
|
+
this.activeProfileId = exports.DEFAULT_PROFILE_ID;
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
// Switching to a non-default profile
|
|
689
|
+
const profile = this.profiles.find(p => p.id.every((x, i) => x === profileId[i]));
|
|
690
|
+
if (!profile) {
|
|
691
|
+
throw new Error('Profile not found.');
|
|
692
|
+
}
|
|
693
|
+
profilePrimaryKey = this.XOR(this.rootPrimaryKey, profile.primaryPad);
|
|
694
|
+
profilePrivilegedPad = profile.privilegedPad;
|
|
695
|
+
this.activeProfileId = profileId;
|
|
696
|
+
}
|
|
697
|
+
// Create a *profile-specific* PrivilegedKeyManager.
|
|
698
|
+
// It uses the ROOT manager internally but applies the profile's pad.
|
|
699
|
+
const profilePrivilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async (reason) => {
|
|
700
|
+
// Request the ROOT privileged key using the root manager
|
|
701
|
+
const rootPrivileged = await this.rootPrivilegedKeyManager.getPrivilegedKey(reason);
|
|
702
|
+
const rootPrivilegedBytes = rootPrivileged.toArray();
|
|
703
|
+
// Apply the profile's pad if applicable
|
|
704
|
+
const profilePrivilegedBytes = profilePrivilegedPad
|
|
705
|
+
? this.XOR(rootPrivilegedBytes, profilePrivilegedPad)
|
|
706
|
+
: rootPrivilegedBytes;
|
|
707
|
+
return new sdk_1.PrivateKey(profilePrivilegedBytes);
|
|
708
|
+
});
|
|
709
|
+
// Build the underlying wallet for the specific profile
|
|
710
|
+
this.underlying = await this.walletBuilder(profilePrimaryKey, profilePrivilegedKeyManager, // Pass the profile-specific manager
|
|
711
|
+
this.activeProfileId // Pass the ID of the profile being activated
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
// --- Key Management Methods ---
|
|
715
|
+
/**
|
|
716
|
+
* Changes the user's password. Re-wraps keys and updates the UMP token.
|
|
717
|
+
*/
|
|
718
|
+
async changePassword(newPassword) {
|
|
719
|
+
if (!this.authenticated || !this.currentUMPToken || !this.rootPrimaryKey || !this.rootPrivilegedKeyManager) {
|
|
720
|
+
throw new Error('Not authenticated or missing required data.');
|
|
581
721
|
}
|
|
582
722
|
const passwordSalt = (0, sdk_1.Random)(32);
|
|
583
|
-
const
|
|
584
|
-
// Decrypt existing factors
|
|
585
|
-
const recoveryKey =
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
ciphertext: this.currentUMPToken.presentationKeyEncrypted,
|
|
592
|
-
protocolID: [2, 'admin key wrapping'],
|
|
593
|
-
keyID: '1'
|
|
594
|
-
})).plaintext;
|
|
595
|
-
const privilegedKey = new sdk_1.SymmetricKey(this.XOR(presentationKey, recoveryKey)).decrypt(this.currentUMPToken.presentationRecoveryPrivileged);
|
|
596
|
-
await this.updateAuthFactors(passwordSalt, passwordKey, presentationKey, recoveryKey, this.primaryKey, privilegedKey);
|
|
723
|
+
const newPasswordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(newPassword, 'utf8'), passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
|
|
724
|
+
// Decrypt existing factors needed for re-encryption, using the *root* privileged key manager
|
|
725
|
+
const recoveryKey = await this.getFactor('recoveryKey');
|
|
726
|
+
const presentationKey = await this.getFactor('presentationKey');
|
|
727
|
+
const rootPrivilegedKey = await this.getFactor('privilegedKey', true); // Get ROOT privileged key
|
|
728
|
+
await this.updateAuthFactors(passwordSalt, newPasswordKey, presentationKey, recoveryKey, this.rootPrimaryKey, rootPrivilegedKey, // Pass the explicitly fetched root key
|
|
729
|
+
this.profiles // Preserve existing profiles
|
|
730
|
+
);
|
|
597
731
|
}
|
|
598
732
|
/**
|
|
599
|
-
* Retrieves the current recovery key.
|
|
600
|
-
*
|
|
601
|
-
* @throws {Error} If the user is not authenticated, or if underlying token references are missing.
|
|
733
|
+
* Retrieves the current recovery key. Requires privileged access.
|
|
602
734
|
*/
|
|
603
735
|
async getRecoveryKey() {
|
|
604
|
-
if (!this.authenticated) {
|
|
605
|
-
throw new Error('Not authenticated.');
|
|
736
|
+
if (!this.authenticated || !this.currentUMPToken || !this.rootPrivilegedKeyManager) {
|
|
737
|
+
throw new Error('Not authenticated or missing required data.');
|
|
606
738
|
}
|
|
607
|
-
|
|
608
|
-
throw new Error('No UMP token!');
|
|
609
|
-
}
|
|
610
|
-
return (await this.underlyingPrivilegedKeyManager.decrypt({
|
|
611
|
-
ciphertext: this.currentUMPToken.recoveryKeyEncrypted,
|
|
612
|
-
protocolID: [2, 'admin key wrapping'],
|
|
613
|
-
keyID: '1'
|
|
614
|
-
})).plaintext;
|
|
739
|
+
return this.getFactor('recoveryKey');
|
|
615
740
|
}
|
|
616
741
|
/**
|
|
617
|
-
* Changes the user's recovery key
|
|
618
|
-
*
|
|
619
|
-
* @throws {Error} If the user is not authenticated, or if underlying token references are missing.
|
|
742
|
+
* Changes the user's recovery key. Prompts user to save the new key.
|
|
620
743
|
*/
|
|
621
744
|
async changeRecoveryKey() {
|
|
622
|
-
if (!this.authenticated) {
|
|
623
|
-
throw new Error('Not authenticated.');
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
ciphertext: this.currentUMPToken.presentationKeyEncrypted,
|
|
636
|
-
protocolID: [2, 'admin key wrapping'],
|
|
637
|
-
keyID: '1'
|
|
638
|
-
})).plaintext;
|
|
639
|
-
const privilegedKey = new sdk_1.SymmetricKey(this.XOR(passwordKey, this.primaryKey)).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
|
|
640
|
-
const recoveryKey = (0, sdk_1.Random)(32);
|
|
641
|
-
await this.recoveryKeySaver(recoveryKey);
|
|
642
|
-
await this.updateAuthFactors(this.currentUMPToken.passwordSalt, passwordKey, presentationKey, recoveryKey, this.primaryKey, privilegedKey);
|
|
745
|
+
if (!this.authenticated || !this.currentUMPToken || !this.rootPrimaryKey || !this.rootPrivilegedKeyManager) {
|
|
746
|
+
throw new Error('Not authenticated or missing required data.');
|
|
747
|
+
}
|
|
748
|
+
// Decrypt existing factors needed
|
|
749
|
+
const passwordKey = await this.getFactor('passwordKey');
|
|
750
|
+
const presentationKey = await this.getFactor('presentationKey');
|
|
751
|
+
const rootPrivilegedKey = await this.getFactor('privilegedKey', true); // Get ROOT privileged key
|
|
752
|
+
// Generate and save new recovery key
|
|
753
|
+
const newRecoveryKey = (0, sdk_1.Random)(32);
|
|
754
|
+
await this.recoveryKeySaver(newRecoveryKey);
|
|
755
|
+
await this.updateAuthFactors(this.currentUMPToken.passwordSalt, passwordKey, presentationKey, newRecoveryKey, // Use the new key
|
|
756
|
+
this.rootPrimaryKey, rootPrivilegedKey, this.profiles // Preserve profiles
|
|
757
|
+
);
|
|
643
758
|
}
|
|
644
759
|
/**
|
|
645
760
|
* Changes the user's presentation key.
|
|
646
|
-
*
|
|
647
|
-
* @param presentationKey The new presentation key (32 bytes).
|
|
648
|
-
* @throws {Error} If the user is not authenticated, or if underlying token references are missing.
|
|
649
761
|
*/
|
|
650
|
-
async changePresentationKey(
|
|
651
|
-
if (!this.authenticated) {
|
|
652
|
-
throw new Error('Not authenticated.');
|
|
762
|
+
async changePresentationKey(newPresentationKey) {
|
|
763
|
+
if (!this.authenticated || !this.currentUMPToken || !this.rootPrimaryKey || !this.rootPrivilegedKeyManager) {
|
|
764
|
+
throw new Error('Not authenticated or missing required data.');
|
|
765
|
+
}
|
|
766
|
+
if (newPresentationKey.length !== 32) {
|
|
767
|
+
throw new Error('Presentation key must be 32 bytes.');
|
|
768
|
+
}
|
|
769
|
+
// Decrypt existing factors
|
|
770
|
+
const recoveryKey = await this.getFactor('recoveryKey');
|
|
771
|
+
const passwordKey = await this.getFactor('passwordKey');
|
|
772
|
+
const rootPrivilegedKey = await this.getFactor('privilegedKey', true); // Get ROOT privileged key
|
|
773
|
+
await this.updateAuthFactors(this.currentUMPToken.passwordSalt, passwordKey, newPresentationKey, // Use the new key
|
|
774
|
+
recoveryKey, this.rootPrimaryKey, rootPrivilegedKey, this.profiles // Preserve profiles
|
|
775
|
+
);
|
|
776
|
+
// Update the temporarily stored key if it was set
|
|
777
|
+
if (this.presentationKey) {
|
|
778
|
+
this.presentationKey = newPresentationKey;
|
|
653
779
|
}
|
|
654
|
-
if (!this.currentUMPToken) {
|
|
655
|
-
throw new Error('No UMP token to update.');
|
|
656
|
-
}
|
|
657
|
-
// Decrypt existing password/recovery keys via the privileged key manager:
|
|
658
|
-
const recoveryKey = (await this.underlyingPrivilegedKeyManager.decrypt({
|
|
659
|
-
ciphertext: this.currentUMPToken.recoveryKeyEncrypted,
|
|
660
|
-
protocolID: [2, 'admin key wrapping'],
|
|
661
|
-
keyID: '1'
|
|
662
|
-
})).plaintext;
|
|
663
|
-
const passwordKey = (await this.underlyingPrivilegedKeyManager.decrypt({
|
|
664
|
-
ciphertext: this.currentUMPToken.passwordKeyEncrypted,
|
|
665
|
-
protocolID: [2, 'admin key wrapping'],
|
|
666
|
-
keyID: '1'
|
|
667
|
-
})).plaintext;
|
|
668
|
-
const privilegedKey = new sdk_1.SymmetricKey(this.XOR(passwordKey, this.primaryKey)).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
|
|
669
|
-
await this.updateAuthFactors(this.currentUMPToken.passwordSalt, passwordKey, presentationKey, recoveryKey, this.primaryKey, privilegedKey);
|
|
670
780
|
}
|
|
781
|
+
// --- Internal Helper Methods ---
|
|
671
782
|
/**
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
* @param passwordSalt The PBKDF2 salt for the new password factor.
|
|
675
|
-
* @param passwordKey The PBKDF2-derived password key (32 bytes).
|
|
676
|
-
* @param presentationKey The new or existing presentation key (32 bytes).
|
|
677
|
-
* @param recoveryKey The new or existing recovery key (32 bytes).
|
|
678
|
-
* @param primaryKey The user's primary key for re-wrapping.
|
|
679
|
-
* @param privilegedKey The user's privileged key for re-wrapping.
|
|
680
|
-
* @throws {Error} If the user is not authenticated or if keys are unavailable.
|
|
783
|
+
* Performs XOR operation on two byte arrays.
|
|
681
784
|
*/
|
|
682
|
-
|
|
683
|
-
if (
|
|
684
|
-
|
|
785
|
+
XOR(n1, n2) {
|
|
786
|
+
if (n1.length !== n2.length) {
|
|
787
|
+
// Provide more context in error
|
|
788
|
+
throw new Error(`XOR length mismatch: ${n1.length} vs ${n2.length}`);
|
|
789
|
+
}
|
|
790
|
+
const r = new Array(n1.length);
|
|
791
|
+
for (let i = 0; i < n1.length; i++) {
|
|
792
|
+
r[i] = n1[i] ^ n2[i];
|
|
793
|
+
}
|
|
794
|
+
return r;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Helper to decrypt a specific factor (key) stored encrypted in the UMP token.
|
|
798
|
+
* Requires the root privileged key manager.
|
|
799
|
+
* @param factorName Name of the factor to decrypt ('passwordKey', 'presentationKey', 'recoveryKey', 'privilegedKey').
|
|
800
|
+
* @param getRoot If true and factorName is 'privilegedKey', returns the root privileged key bytes directly.
|
|
801
|
+
* @returns The decrypted key bytes.
|
|
802
|
+
*/
|
|
803
|
+
async getFactor(factorName, getRoot = false) {
|
|
804
|
+
if (!this.authenticated || !this.currentUMPToken || !this.rootPrivilegedKeyManager) {
|
|
805
|
+
throw new Error(`Cannot get factor "${factorName}": Wallet not ready.`);
|
|
806
|
+
}
|
|
807
|
+
const protocolID = [2, 'admin key wrapping']; // Protocol used for encrypting factors
|
|
808
|
+
const keyID = '1'; // Key ID used
|
|
809
|
+
try {
|
|
810
|
+
switch (factorName) {
|
|
811
|
+
case 'passwordKey':
|
|
812
|
+
return (await this.rootPrivilegedKeyManager.decrypt({
|
|
813
|
+
ciphertext: this.currentUMPToken.passwordKeyEncrypted,
|
|
814
|
+
protocolID,
|
|
815
|
+
keyID
|
|
816
|
+
})).plaintext;
|
|
817
|
+
case 'presentationKey':
|
|
818
|
+
return (await this.rootPrivilegedKeyManager.decrypt({
|
|
819
|
+
ciphertext: this.currentUMPToken.presentationKeyEncrypted,
|
|
820
|
+
protocolID,
|
|
821
|
+
keyID
|
|
822
|
+
})).plaintext;
|
|
823
|
+
case 'recoveryKey':
|
|
824
|
+
return (await this.rootPrivilegedKeyManager.decrypt({
|
|
825
|
+
ciphertext: this.currentUMPToken.recoveryKeyEncrypted,
|
|
826
|
+
protocolID,
|
|
827
|
+
keyID
|
|
828
|
+
})).plaintext;
|
|
829
|
+
case 'privilegedKey': {
|
|
830
|
+
// This needs careful handling based on whether the ROOT or PROFILE key is needed.
|
|
831
|
+
// This helper is mostly used for UMP updates, which need the ROOT key.
|
|
832
|
+
// We retrieve the PrivateKey object first.
|
|
833
|
+
const pk = await this.rootPrivilegedKeyManager.getPrivilegedKey('UMP token update', true); // Force retrieval of root key
|
|
834
|
+
return pk.toArray(); // Return bytes
|
|
835
|
+
}
|
|
836
|
+
default:
|
|
837
|
+
throw new Error(`Unknown factor name: ${factorName}`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
console.error(`Error decrypting factor ${factorName}:`, error);
|
|
842
|
+
throw new Error(`Failed to decrypt factor "${factorName}": ${error.message}`);
|
|
685
843
|
}
|
|
686
|
-
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Recomputes UMP token fields with updated factors and profiles, then publishes the update.
|
|
847
|
+
* This operation requires the *root* privileged key and the *default* profile wallet.
|
|
848
|
+
*/
|
|
849
|
+
async updateAuthFactors(passwordSalt, passwordKey, presentationKey, recoveryKey, rootPrimaryKey, rootPrivilegedKey, // Explicitly pass the root key bytes
|
|
850
|
+
profiles // Pass current/new profiles list
|
|
851
|
+
) {
|
|
852
|
+
if (!this.authenticated || !this.rootPrimaryKey || !this.currentUMPToken) {
|
|
853
|
+
throw new Error('Wallet is not properly authenticated or missing data for update.');
|
|
854
|
+
}
|
|
855
|
+
// Ensure we have the OLD token to consume
|
|
856
|
+
const oldTokenToConsume = { ...this.currentUMPToken };
|
|
857
|
+
if (!oldTokenToConsume.currentOutpoint) {
|
|
858
|
+
throw new Error('Cannot update UMP token: Old token has no outpoint.');
|
|
859
|
+
}
|
|
860
|
+
// Derive symmetrical encryption keys using XOR for the *root* keys
|
|
687
861
|
const presentationPassword = new sdk_1.SymmetricKey(this.XOR(presentationKey, passwordKey));
|
|
688
862
|
const presentationRecovery = new sdk_1.SymmetricKey(this.XOR(presentationKey, recoveryKey));
|
|
689
863
|
const recoveryPassword = new sdk_1.SymmetricKey(this.XOR(recoveryKey, passwordKey));
|
|
690
|
-
const primaryPassword = new sdk_1.SymmetricKey(this.XOR(
|
|
691
|
-
// Build a temporary privileged key manager
|
|
692
|
-
const
|
|
693
|
-
//
|
|
694
|
-
|
|
864
|
+
const primaryPassword = new sdk_1.SymmetricKey(this.XOR(rootPrimaryKey, passwordKey)); // Use rootPrimaryKey
|
|
865
|
+
// Build a temporary privileged key manager using the explicit ROOT privileged key
|
|
866
|
+
const tempRootPrivilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async () => new sdk_1.PrivateKey(rootPrivilegedKey));
|
|
867
|
+
// Encrypt profiles if provided
|
|
868
|
+
let profilesEncrypted;
|
|
869
|
+
if (profiles && profiles.length > 0) {
|
|
870
|
+
const profilesJson = JSON.stringify(profiles);
|
|
871
|
+
const profilesBytes = sdk_1.Utils.toArray(profilesJson, 'utf8');
|
|
872
|
+
profilesEncrypted = (await tempRootPrivilegedKeyManager.encrypt({
|
|
873
|
+
plaintext: profilesBytes,
|
|
874
|
+
protocolID: [2, 'admin profile wrapping'], // Separate protocol for profiles
|
|
875
|
+
keyID: '1'
|
|
876
|
+
})).ciphertext;
|
|
877
|
+
}
|
|
878
|
+
// Construct the new UMP token data
|
|
879
|
+
const newTokenData = {
|
|
695
880
|
passwordSalt,
|
|
696
|
-
passwordPresentationPrimary: presentationPassword.encrypt(
|
|
697
|
-
passwordRecoveryPrimary: recoveryPassword.encrypt(
|
|
698
|
-
presentationRecoveryPrimary: presentationRecovery.encrypt(
|
|
699
|
-
passwordPrimaryPrivileged: primaryPassword.encrypt(
|
|
700
|
-
presentationRecoveryPrivileged: presentationRecovery.encrypt(
|
|
881
|
+
passwordPresentationPrimary: presentationPassword.encrypt(rootPrimaryKey),
|
|
882
|
+
passwordRecoveryPrimary: recoveryPassword.encrypt(rootPrimaryKey),
|
|
883
|
+
presentationRecoveryPrimary: presentationRecovery.encrypt(rootPrimaryKey),
|
|
884
|
+
passwordPrimaryPrivileged: primaryPassword.encrypt(rootPrivilegedKey),
|
|
885
|
+
presentationRecoveryPrivileged: presentationRecovery.encrypt(rootPrivilegedKey),
|
|
701
886
|
presentationHash: sdk_1.Hash.sha256(presentationKey),
|
|
702
887
|
recoveryHash: sdk_1.Hash.sha256(recoveryKey),
|
|
703
|
-
presentationKeyEncrypted: (await
|
|
888
|
+
presentationKeyEncrypted: (await tempRootPrivilegedKeyManager.encrypt({
|
|
704
889
|
plaintext: presentationKey,
|
|
705
890
|
protocolID: [2, 'admin key wrapping'],
|
|
706
891
|
keyID: '1'
|
|
707
892
|
})).ciphertext,
|
|
708
|
-
passwordKeyEncrypted: (await
|
|
893
|
+
passwordKeyEncrypted: (await tempRootPrivilegedKeyManager.encrypt({
|
|
709
894
|
plaintext: passwordKey,
|
|
710
895
|
protocolID: [2, 'admin key wrapping'],
|
|
711
896
|
keyID: '1'
|
|
712
897
|
})).ciphertext,
|
|
713
|
-
recoveryKeyEncrypted: (await
|
|
898
|
+
recoveryKeyEncrypted: (await tempRootPrivilegedKeyManager.encrypt({
|
|
714
899
|
plaintext: recoveryKey,
|
|
715
900
|
protocolID: [2, 'admin key wrapping'],
|
|
716
901
|
keyID: '1'
|
|
717
|
-
})).ciphertext
|
|
902
|
+
})).ciphertext,
|
|
903
|
+
profilesEncrypted // Add encrypted profiles
|
|
904
|
+
// currentOutpoint will be set after publishing
|
|
718
905
|
};
|
|
719
|
-
//
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
906
|
+
// We need the wallet built for the DEFAULT profile to publish the UMP token.
|
|
907
|
+
// If the current active profile is not default, temporarily switch, publish, then switch back.
|
|
908
|
+
const currentActiveId = this.activeProfileId;
|
|
909
|
+
let walletToUse = this.underlying;
|
|
910
|
+
if (currentActiveId.every(x => x === 0)) {
|
|
911
|
+
console.log('Temporarily switching to default profile to update UMP token...');
|
|
912
|
+
await this.switchProfile(exports.DEFAULT_PROFILE_ID); // This rebuilds this.underlying
|
|
913
|
+
walletToUse = this.underlying;
|
|
914
|
+
}
|
|
915
|
+
if (!walletToUse) {
|
|
916
|
+
throw new Error('Default profile wallet could not be activated for UMP token update.');
|
|
917
|
+
}
|
|
918
|
+
// Publish the new token on-chain, consuming the old one
|
|
919
|
+
try {
|
|
920
|
+
newTokenData.currentOutpoint = await this.UMPTokenInteractor.buildAndSend(walletToUse, // Use the (potentially temporarily activated) default profile wallet
|
|
921
|
+
this.adminOriginator, newTokenData, oldTokenToConsume // Consume the previous token
|
|
922
|
+
);
|
|
923
|
+
// Update the manager's state
|
|
924
|
+
this.currentUMPToken = newTokenData;
|
|
925
|
+
// Profiles are already updated in this.profiles if they were passed in
|
|
926
|
+
}
|
|
927
|
+
finally {
|
|
928
|
+
// Switch back if we temporarily switched
|
|
929
|
+
if (!currentActiveId.every(x => x === 0)) {
|
|
930
|
+
console.log('Switching back to original profile...');
|
|
931
|
+
await this.switchProfile(currentActiveId);
|
|
932
|
+
}
|
|
738
933
|
}
|
|
739
|
-
return r;
|
|
740
934
|
}
|
|
741
935
|
/**
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
* - [1 byte version (value=1)]
|
|
745
|
-
* - For each array field in the UMP token, [varint length + bytes]
|
|
746
|
-
* - Then [varint length + outpoint string in UTF-8]
|
|
747
|
-
*
|
|
748
|
-
* @param token The UMP token to serialize.
|
|
749
|
-
* @returns A byte array representing the serialized token.
|
|
750
|
-
* @throws {Error} if the token has no currentOutpoint (required for serialization).
|
|
936
|
+
* Serializes a UMP token to binary format (Version 2 with optional profiles).
|
|
937
|
+
* 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]
|
|
751
938
|
*/
|
|
752
939
|
serializeUMPToken(token) {
|
|
753
940
|
if (!token.currentOutpoint) {
|
|
754
941
|
throw new Error('Token must have outpoint for serialization');
|
|
755
942
|
}
|
|
756
943
|
const writer = new sdk_1.Utils.Writer();
|
|
757
|
-
//
|
|
758
|
-
writer.writeUInt8(1);
|
|
759
|
-
// Helper to write array with length prefix
|
|
944
|
+
writer.writeUInt8(2); // Version 2
|
|
760
945
|
const writeArray = (arr) => {
|
|
761
946
|
writer.writeVarIntNum(arr.length);
|
|
762
947
|
writer.write(arr);
|
|
763
948
|
};
|
|
764
|
-
// Write
|
|
765
|
-
writeArray(token.
|
|
766
|
-
writeArray(token.
|
|
767
|
-
writeArray(token.
|
|
768
|
-
writeArray(token.
|
|
769
|
-
writeArray(token.
|
|
770
|
-
writeArray(token.
|
|
771
|
-
writeArray(token.
|
|
772
|
-
writeArray(token.recoveryHash);
|
|
773
|
-
writeArray(token.presentationKeyEncrypted);
|
|
774
|
-
writeArray(token.
|
|
775
|
-
writeArray(token.
|
|
776
|
-
//
|
|
949
|
+
// Write standard fields in specific order
|
|
950
|
+
writeArray(token.passwordSalt); // 0
|
|
951
|
+
writeArray(token.passwordPresentationPrimary); // 1
|
|
952
|
+
writeArray(token.passwordRecoveryPrimary); // 2
|
|
953
|
+
writeArray(token.presentationRecoveryPrimary); // 3
|
|
954
|
+
writeArray(token.passwordPrimaryPrivileged); // 4
|
|
955
|
+
writeArray(token.presentationRecoveryPrivileged); // 5
|
|
956
|
+
writeArray(token.presentationHash); // 6
|
|
957
|
+
writeArray(token.recoveryHash); // 7
|
|
958
|
+
writeArray(token.presentationKeyEncrypted); // 8
|
|
959
|
+
writeArray(token.passwordKeyEncrypted); // 9 - Swapped order vs original doc comment
|
|
960
|
+
writeArray(token.recoveryKeyEncrypted); // 10
|
|
961
|
+
// Write optional profiles field
|
|
962
|
+
if (token.profilesEncrypted && token.profilesEncrypted.length > 0) {
|
|
963
|
+
writer.writeUInt8(1); // Flag indicating profiles present
|
|
964
|
+
writeArray(token.profilesEncrypted);
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
writer.writeUInt8(0); // Flag indicating no profiles
|
|
968
|
+
}
|
|
969
|
+
// Write outpoint string
|
|
777
970
|
const outpointBytes = sdk_1.Utils.toArray(token.currentOutpoint, 'utf8');
|
|
778
971
|
writer.writeVarIntNum(outpointBytes.length);
|
|
779
972
|
writer.write(outpointBytes);
|
|
780
973
|
return writer.toArray();
|
|
781
974
|
}
|
|
782
975
|
/**
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
* @param bin The serialized byte array.
|
|
786
|
-
* @returns The reconstructed UMP token.
|
|
787
|
-
* @throws {Error} if the version byte is unexpected or if parsing fails.
|
|
976
|
+
* Deserializes a UMP token from binary format (Handles Version 1 and 2).
|
|
788
977
|
*/
|
|
789
978
|
deserializeUMPToken(bin) {
|
|
790
979
|
const reader = new sdk_1.Utils.Reader(bin);
|
|
791
|
-
// Check version:
|
|
792
980
|
const version = reader.readUInt8();
|
|
793
|
-
if (version !== 1) {
|
|
794
|
-
throw new Error(`Unsupported UMP token version: ${version}`);
|
|
981
|
+
if (version !== 1 && version !== 2) {
|
|
982
|
+
throw new Error(`Unsupported UMP token serialization version: ${version}`);
|
|
795
983
|
}
|
|
796
|
-
// Helper to read an array with length prefix
|
|
797
984
|
const readArray = () => {
|
|
798
985
|
const length = reader.readVarIntNum();
|
|
799
986
|
return reader.read(length);
|
|
800
987
|
};
|
|
801
|
-
// Read
|
|
802
|
-
const
|
|
803
|
-
const
|
|
804
|
-
const
|
|
805
|
-
const
|
|
806
|
-
const
|
|
807
|
-
const
|
|
808
|
-
const
|
|
809
|
-
const recoveryHash = readArray();
|
|
810
|
-
const presentationKeyEncrypted = readArray();
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
// Read
|
|
988
|
+
// Read standard fields (order matches serialization V2)
|
|
989
|
+
const passwordSalt = readArray(); // 0
|
|
990
|
+
const passwordPresentationPrimary = readArray(); // 1
|
|
991
|
+
const passwordRecoveryPrimary = readArray(); // 2
|
|
992
|
+
const presentationRecoveryPrimary = readArray(); // 3
|
|
993
|
+
const passwordPrimaryPrivileged = readArray(); // 4
|
|
994
|
+
const presentationRecoveryPrivileged = readArray(); // 5
|
|
995
|
+
const presentationHash = readArray(); // 6
|
|
996
|
+
const recoveryHash = readArray(); // 7
|
|
997
|
+
const presentationKeyEncrypted = readArray(); // 8
|
|
998
|
+
const passwordKeyEncrypted = readArray(); // 9
|
|
999
|
+
const recoveryKeyEncrypted = readArray(); // 10
|
|
1000
|
+
// Read optional profiles (only in V2)
|
|
1001
|
+
let profilesEncrypted;
|
|
1002
|
+
if (version === 2) {
|
|
1003
|
+
const profilesFlag = reader.readUInt8();
|
|
1004
|
+
if (profilesFlag === 1) {
|
|
1005
|
+
profilesEncrypted = readArray();
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
// Read outpoint string
|
|
814
1009
|
const outpointLen = reader.readVarIntNum();
|
|
815
1010
|
const outpointBytes = reader.read(outpointLen);
|
|
816
1011
|
const currentOutpoint = sdk_1.Utils.toUTF8(outpointBytes);
|
|
817
1012
|
const token = {
|
|
1013
|
+
passwordSalt,
|
|
818
1014
|
passwordPresentationPrimary,
|
|
819
1015
|
passwordRecoveryPrimary,
|
|
820
1016
|
presentationRecoveryPrimary,
|
|
821
1017
|
passwordPrimaryPrivileged,
|
|
822
1018
|
presentationRecoveryPrivileged,
|
|
823
1019
|
presentationHash,
|
|
824
|
-
passwordSalt,
|
|
825
1020
|
recoveryHash,
|
|
826
1021
|
presentationKeyEncrypted,
|
|
1022
|
+
passwordKeyEncrypted, // Corrected order
|
|
827
1023
|
recoveryKeyEncrypted,
|
|
828
|
-
|
|
1024
|
+
profilesEncrypted, // May be undefined
|
|
829
1025
|
currentOutpoint
|
|
830
1026
|
};
|
|
831
1027
|
return token;
|
|
832
1028
|
}
|
|
833
1029
|
/**
|
|
834
|
-
*
|
|
1030
|
+
* Sets up the root key infrastructure after authentication or loading from snapshot.
|
|
1031
|
+
* Initializes the root primary key, root privileged key manager, loads profiles,
|
|
1032
|
+
* and sets the authenticated flag. Does NOT switch profile initially.
|
|
835
1033
|
*
|
|
836
|
-
* @param
|
|
837
|
-
* @param
|
|
1034
|
+
* @param rootPrimaryKey The user's root primary key (32 bytes).
|
|
1035
|
+
* @param ephemeralRootPrivilegedKey Optional root privileged key (e.g., during recovery flows).
|
|
838
1036
|
*/
|
|
839
|
-
async
|
|
1037
|
+
async setupRootInfrastructure(rootPrimaryKey, ephemeralRootPrivilegedKey) {
|
|
840
1038
|
if (!this.currentUMPToken) {
|
|
841
|
-
throw new Error('A UMP token must exist before
|
|
842
|
-
}
|
|
843
|
-
this.
|
|
844
|
-
//
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1039
|
+
throw new Error('A UMP token must exist before setting up root infrastructure!');
|
|
1040
|
+
}
|
|
1041
|
+
this.rootPrimaryKey = rootPrimaryKey;
|
|
1042
|
+
// Store ephemeral key if provided, for one-time use by the manager
|
|
1043
|
+
let oneTimePrivilegedKey = ephemeralRootPrivilegedKey
|
|
1044
|
+
? new sdk_1.PrivateKey(ephemeralRootPrivilegedKey)
|
|
1045
|
+
: undefined;
|
|
1046
|
+
// Create the ROOT PrivilegedKeyManager
|
|
1047
|
+
this.rootPrivilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async (reason) => {
|
|
1048
|
+
// 1. Use one-time key if available (for recovery)
|
|
1049
|
+
if (oneTimePrivilegedKey) {
|
|
1050
|
+
const tempKey = oneTimePrivilegedKey;
|
|
1051
|
+
oneTimePrivilegedKey = undefined; // Consume it
|
|
851
1052
|
return tempKey;
|
|
852
1053
|
}
|
|
853
|
-
// Otherwise,
|
|
1054
|
+
// 2. Otherwise, derive from password
|
|
854
1055
|
const password = await this.passwordRetriever(reason, (passwordCandidate) => {
|
|
855
1056
|
try {
|
|
856
1057
|
const derivedPasswordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(passwordCandidate, 'utf8'), this.currentUMPToken.passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
|
|
857
|
-
|
|
858
|
-
const privilegedDecryptor = this.XOR(this.primaryKey, derivedPasswordKey);
|
|
1058
|
+
const privilegedDecryptor = this.XOR(this.rootPrimaryKey, derivedPasswordKey);
|
|
859
1059
|
const decryptedPrivileged = new sdk_1.SymmetricKey(privilegedDecryptor).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
|
|
860
|
-
|
|
861
|
-
return true;
|
|
862
|
-
}
|
|
863
|
-
return false;
|
|
1060
|
+
return !!decryptedPrivileged; // Test passes if decryption works
|
|
864
1061
|
}
|
|
865
1062
|
catch (e) {
|
|
866
1063
|
return false;
|
|
867
1064
|
}
|
|
868
1065
|
});
|
|
1066
|
+
// Decrypt the root privileged key using the confirmed password
|
|
869
1067
|
const derivedPasswordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(password, 'utf8'), this.currentUMPToken.passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
|
|
870
|
-
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
return new sdk_1.PrivateKey(decryptedPrivileged);
|
|
1068
|
+
const privilegedDecryptor = this.XOR(this.rootPrimaryKey, derivedPasswordKey);
|
|
1069
|
+
const rootPrivilegedBytes = new sdk_1.SymmetricKey(privilegedDecryptor).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
|
|
1070
|
+
return new sdk_1.PrivateKey(rootPrivilegedBytes); // Return the ROOT key object
|
|
874
1071
|
});
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
this.
|
|
1072
|
+
// Decrypt and load profiles if present in the token
|
|
1073
|
+
this.profiles = []; // Clear existing profiles before loading
|
|
1074
|
+
if (this.currentUMPToken.profilesEncrypted && this.currentUMPToken.profilesEncrypted.length > 0) {
|
|
1075
|
+
try {
|
|
1076
|
+
const decryptedProfileBytes = (await this.rootPrivilegedKeyManager.decrypt({
|
|
1077
|
+
ciphertext: this.currentUMPToken.profilesEncrypted,
|
|
1078
|
+
protocolID: [2, 'admin profile wrapping'], // Use profile protocol ID
|
|
1079
|
+
keyID: '1'
|
|
1080
|
+
})).plaintext;
|
|
1081
|
+
const profilesJson = sdk_1.Utils.toUTF8(decryptedProfileBytes);
|
|
1082
|
+
this.profiles = JSON.parse(profilesJson);
|
|
1083
|
+
}
|
|
1084
|
+
catch (error) {
|
|
1085
|
+
console.error('Failed to decrypt or parse profiles:', error);
|
|
1086
|
+
// Decide if this should be fatal or just log and continue without profiles
|
|
1087
|
+
this.profiles = []; // Ensure profiles are empty on error
|
|
1088
|
+
// Optionally re-throw or handle more gracefully
|
|
1089
|
+
throw new Error(`Failed to load profiles: ${error.message}`);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
878
1092
|
this.authenticated = true;
|
|
1093
|
+
// Note: We don't call switchProfile here anymore.
|
|
1094
|
+
// It's called by the auth methods (providePassword/provideRecoveryKey) or loadSnapshot after this.
|
|
879
1095
|
}
|
|
880
1096
|
/*
|
|
881
1097
|
* ---------------------------------------------------------------------------------------
|
|
882
|
-
*
|
|
883
|
-
*
|
|
1098
|
+
* Standard WalletInterface methods proxying to the *active* underlying wallet.
|
|
1099
|
+
* Includes authentication checks and admin originator protection.
|
|
884
1100
|
* ---------------------------------------------------------------------------------------
|
|
885
1101
|
*/
|
|
886
|
-
|
|
1102
|
+
checkAuthAndUnderlying(originator) {
|
|
887
1103
|
if (!this.authenticated) {
|
|
888
1104
|
throw new Error('User is not authenticated.');
|
|
889
1105
|
}
|
|
1106
|
+
if (!this.underlying) {
|
|
1107
|
+
// This might happen if authentication succeeded but profile switching failed
|
|
1108
|
+
throw new Error('Underlying wallet for the active profile is not initialized.');
|
|
1109
|
+
}
|
|
890
1110
|
if (originator === this.adminOriginator) {
|
|
891
1111
|
throw new Error('External applications are not allowed to use the admin originator.');
|
|
892
1112
|
}
|
|
1113
|
+
}
|
|
1114
|
+
// Example proxy method (repeat pattern for all others)
|
|
1115
|
+
async getPublicKey(args, originator) {
|
|
1116
|
+
this.checkAuthAndUnderlying(originator);
|
|
893
1117
|
return this.underlying.getPublicKey(args, originator);
|
|
894
1118
|
}
|
|
895
1119
|
async revealCounterpartyKeyLinkage(args, originator) {
|
|
896
|
-
|
|
897
|
-
throw new Error('User is not authenticated.');
|
|
898
|
-
}
|
|
899
|
-
if (originator === this.adminOriginator) {
|
|
900
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
901
|
-
}
|
|
1120
|
+
this.checkAuthAndUnderlying(originator);
|
|
902
1121
|
return this.underlying.revealCounterpartyKeyLinkage(args, originator);
|
|
903
1122
|
}
|
|
904
1123
|
async revealSpecificKeyLinkage(args, originator) {
|
|
905
|
-
|
|
906
|
-
throw new Error('User is not authenticated.');
|
|
907
|
-
}
|
|
908
|
-
if (originator === this.adminOriginator) {
|
|
909
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
910
|
-
}
|
|
1124
|
+
this.checkAuthAndUnderlying(originator);
|
|
911
1125
|
return this.underlying.revealSpecificKeyLinkage(args, originator);
|
|
912
1126
|
}
|
|
913
1127
|
async encrypt(args, originator) {
|
|
914
|
-
|
|
915
|
-
throw new Error('User is not authenticated.');
|
|
916
|
-
}
|
|
917
|
-
if (originator === this.adminOriginator) {
|
|
918
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
919
|
-
}
|
|
1128
|
+
this.checkAuthAndUnderlying(originator);
|
|
920
1129
|
return this.underlying.encrypt(args, originator);
|
|
921
1130
|
}
|
|
922
1131
|
async decrypt(args, originator) {
|
|
923
|
-
|
|
924
|
-
throw new Error('User is not authenticated.');
|
|
925
|
-
}
|
|
926
|
-
if (originator === this.adminOriginator) {
|
|
927
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
928
|
-
}
|
|
1132
|
+
this.checkAuthAndUnderlying(originator);
|
|
929
1133
|
return this.underlying.decrypt(args, originator);
|
|
930
1134
|
}
|
|
931
1135
|
async createHmac(args, originator) {
|
|
932
|
-
|
|
933
|
-
throw new Error('User is not authenticated.');
|
|
934
|
-
}
|
|
935
|
-
if (originator === this.adminOriginator) {
|
|
936
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
937
|
-
}
|
|
1136
|
+
this.checkAuthAndUnderlying(originator);
|
|
938
1137
|
return this.underlying.createHmac(args, originator);
|
|
939
1138
|
}
|
|
940
1139
|
async verifyHmac(args, originator) {
|
|
941
|
-
|
|
942
|
-
throw new Error('User is not authenticated.');
|
|
943
|
-
}
|
|
944
|
-
if (originator === this.adminOriginator) {
|
|
945
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
946
|
-
}
|
|
1140
|
+
this.checkAuthAndUnderlying(originator);
|
|
947
1141
|
return this.underlying.verifyHmac(args, originator);
|
|
948
1142
|
}
|
|
949
1143
|
async createSignature(args, originator) {
|
|
950
|
-
|
|
951
|
-
throw new Error('User is not authenticated.');
|
|
952
|
-
}
|
|
953
|
-
if (originator === this.adminOriginator) {
|
|
954
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
955
|
-
}
|
|
1144
|
+
this.checkAuthAndUnderlying(originator);
|
|
956
1145
|
return this.underlying.createSignature(args, originator);
|
|
957
1146
|
}
|
|
958
1147
|
async verifySignature(args, originator) {
|
|
959
|
-
|
|
960
|
-
throw new Error('User is not authenticated.');
|
|
961
|
-
}
|
|
962
|
-
if (originator === this.adminOriginator) {
|
|
963
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
964
|
-
}
|
|
1148
|
+
this.checkAuthAndUnderlying(originator);
|
|
965
1149
|
return this.underlying.verifySignature(args, originator);
|
|
966
1150
|
}
|
|
967
1151
|
async createAction(args, originator) {
|
|
968
|
-
|
|
969
|
-
throw new Error('User is not authenticated.');
|
|
970
|
-
}
|
|
971
|
-
if (originator === this.adminOriginator) {
|
|
972
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
973
|
-
}
|
|
1152
|
+
this.checkAuthAndUnderlying(originator);
|
|
974
1153
|
return this.underlying.createAction(args, originator);
|
|
975
1154
|
}
|
|
976
1155
|
async signAction(args, originator) {
|
|
977
|
-
|
|
978
|
-
throw new Error('User is not authenticated.');
|
|
979
|
-
}
|
|
980
|
-
if (originator === this.adminOriginator) {
|
|
981
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
982
|
-
}
|
|
1156
|
+
this.checkAuthAndUnderlying(originator);
|
|
983
1157
|
return this.underlying.signAction(args, originator);
|
|
984
1158
|
}
|
|
985
1159
|
async abortAction(args, originator) {
|
|
986
|
-
|
|
987
|
-
throw new Error('User is not authenticated.');
|
|
988
|
-
}
|
|
989
|
-
if (originator === this.adminOriginator) {
|
|
990
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
991
|
-
}
|
|
1160
|
+
this.checkAuthAndUnderlying(originator);
|
|
992
1161
|
return this.underlying.abortAction(args, originator);
|
|
993
1162
|
}
|
|
994
1163
|
async listActions(args, originator) {
|
|
995
|
-
|
|
996
|
-
throw new Error('User is not authenticated.');
|
|
997
|
-
}
|
|
998
|
-
if (originator === this.adminOriginator) {
|
|
999
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1000
|
-
}
|
|
1164
|
+
this.checkAuthAndUnderlying(originator);
|
|
1001
1165
|
return this.underlying.listActions(args, originator);
|
|
1002
1166
|
}
|
|
1003
1167
|
async internalizeAction(args, originator) {
|
|
1004
|
-
|
|
1005
|
-
throw new Error('User is not authenticated.');
|
|
1006
|
-
}
|
|
1007
|
-
if (originator === this.adminOriginator) {
|
|
1008
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1009
|
-
}
|
|
1168
|
+
this.checkAuthAndUnderlying(originator);
|
|
1010
1169
|
return this.underlying.internalizeAction(args, originator);
|
|
1011
1170
|
}
|
|
1012
1171
|
async listOutputs(args, originator) {
|
|
1013
|
-
|
|
1014
|
-
throw new Error('User is not authenticated.');
|
|
1015
|
-
}
|
|
1016
|
-
if (originator === this.adminOriginator) {
|
|
1017
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1018
|
-
}
|
|
1172
|
+
this.checkAuthAndUnderlying(originator);
|
|
1019
1173
|
return this.underlying.listOutputs(args, originator);
|
|
1020
1174
|
}
|
|
1021
1175
|
async relinquishOutput(args, originator) {
|
|
1022
|
-
|
|
1023
|
-
throw new Error('User is not authenticated.');
|
|
1024
|
-
}
|
|
1025
|
-
if (originator === this.adminOriginator) {
|
|
1026
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1027
|
-
}
|
|
1176
|
+
this.checkAuthAndUnderlying(originator);
|
|
1028
1177
|
return this.underlying.relinquishOutput(args, originator);
|
|
1029
1178
|
}
|
|
1030
1179
|
async acquireCertificate(args, originator) {
|
|
1031
|
-
|
|
1032
|
-
throw new Error('User is not authenticated.');
|
|
1033
|
-
}
|
|
1034
|
-
if (originator === this.adminOriginator) {
|
|
1035
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1036
|
-
}
|
|
1180
|
+
this.checkAuthAndUnderlying(originator);
|
|
1037
1181
|
return this.underlying.acquireCertificate(args, originator);
|
|
1038
1182
|
}
|
|
1039
1183
|
async listCertificates(args, originator) {
|
|
1040
|
-
|
|
1041
|
-
throw new Error('User is not authenticated.');
|
|
1042
|
-
}
|
|
1043
|
-
if (originator === this.adminOriginator) {
|
|
1044
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1045
|
-
}
|
|
1184
|
+
this.checkAuthAndUnderlying(originator);
|
|
1046
1185
|
return this.underlying.listCertificates(args, originator);
|
|
1047
1186
|
}
|
|
1048
1187
|
async proveCertificate(args, originator) {
|
|
1049
|
-
|
|
1050
|
-
throw new Error('User is not authenticated.');
|
|
1051
|
-
}
|
|
1052
|
-
if (originator === this.adminOriginator) {
|
|
1053
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1054
|
-
}
|
|
1188
|
+
this.checkAuthAndUnderlying(originator);
|
|
1055
1189
|
return this.underlying.proveCertificate(args, originator);
|
|
1056
1190
|
}
|
|
1057
1191
|
async relinquishCertificate(args, originator) {
|
|
1058
|
-
|
|
1059
|
-
throw new Error('User is not authenticated.');
|
|
1060
|
-
}
|
|
1061
|
-
if (originator === this.adminOriginator) {
|
|
1062
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1063
|
-
}
|
|
1192
|
+
this.checkAuthAndUnderlying(originator);
|
|
1064
1193
|
return this.underlying.relinquishCertificate(args, originator);
|
|
1065
1194
|
}
|
|
1066
1195
|
async discoverByIdentityKey(args, originator) {
|
|
1067
|
-
|
|
1068
|
-
throw new Error('User is not authenticated.');
|
|
1069
|
-
}
|
|
1070
|
-
if (originator === this.adminOriginator) {
|
|
1071
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1072
|
-
}
|
|
1196
|
+
this.checkAuthAndUnderlying(originator);
|
|
1073
1197
|
return this.underlying.discoverByIdentityKey(args, originator);
|
|
1074
1198
|
}
|
|
1075
1199
|
async discoverByAttributes(args, originator) {
|
|
1076
|
-
|
|
1077
|
-
throw new Error('User is not authenticated.');
|
|
1078
|
-
}
|
|
1079
|
-
if (originator === this.adminOriginator) {
|
|
1080
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1081
|
-
}
|
|
1200
|
+
this.checkAuthAndUnderlying(originator);
|
|
1082
1201
|
return this.underlying.discoverByAttributes(args, originator);
|
|
1083
1202
|
}
|
|
1084
1203
|
async isAuthenticated(_, originator) {
|
|
@@ -1094,45 +1213,25 @@ class CWIStyleWalletManager {
|
|
|
1094
1213
|
if (originator === this.adminOriginator) {
|
|
1095
1214
|
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1096
1215
|
}
|
|
1097
|
-
while (!this.authenticated) {
|
|
1216
|
+
while (!this.authenticated || !this.underlying) {
|
|
1098
1217
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1099
1218
|
}
|
|
1100
1219
|
return { authenticated: true };
|
|
1101
1220
|
}
|
|
1102
1221
|
async getHeight(_, originator) {
|
|
1103
|
-
|
|
1104
|
-
throw new Error('User is not authenticated.');
|
|
1105
|
-
}
|
|
1106
|
-
if (originator === this.adminOriginator) {
|
|
1107
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1108
|
-
}
|
|
1222
|
+
this.checkAuthAndUnderlying(originator);
|
|
1109
1223
|
return this.underlying.getHeight({}, originator);
|
|
1110
1224
|
}
|
|
1111
1225
|
async getHeaderForHeight(args, originator) {
|
|
1112
|
-
|
|
1113
|
-
throw new Error('User is not authenticated.');
|
|
1114
|
-
}
|
|
1115
|
-
if (originator === this.adminOriginator) {
|
|
1116
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1117
|
-
}
|
|
1226
|
+
this.checkAuthAndUnderlying(originator);
|
|
1118
1227
|
return this.underlying.getHeaderForHeight(args, originator);
|
|
1119
1228
|
}
|
|
1120
1229
|
async getNetwork(_, originator) {
|
|
1121
|
-
|
|
1122
|
-
throw new Error('User is not authenticated.');
|
|
1123
|
-
}
|
|
1124
|
-
if (originator === this.adminOriginator) {
|
|
1125
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1126
|
-
}
|
|
1230
|
+
this.checkAuthAndUnderlying(originator);
|
|
1127
1231
|
return this.underlying.getNetwork({}, originator);
|
|
1128
1232
|
}
|
|
1129
1233
|
async getVersion(_, originator) {
|
|
1130
|
-
|
|
1131
|
-
throw new Error('User is not authenticated.');
|
|
1132
|
-
}
|
|
1133
|
-
if (originator === this.adminOriginator) {
|
|
1134
|
-
throw new Error('External applications are not allowed to use the admin originator.');
|
|
1135
|
-
}
|
|
1234
|
+
this.checkAuthAndUnderlying(originator);
|
|
1136
1235
|
return this.underlying.getVersion({}, originator);
|
|
1137
1236
|
}
|
|
1138
1237
|
}
|