@metamask-previews/seedless-onboarding-controller 0.0.0-preview-ddbcf048
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/CHANGELOG.md +20 -0
- package/LICENSE +20 -0
- package/README.md +15 -0
- package/dist/SeedPhraseMetadata.cjs +146 -0
- package/dist/SeedPhraseMetadata.cjs.map +1 -0
- package/dist/SeedPhraseMetadata.d.cts +82 -0
- package/dist/SeedPhraseMetadata.d.cts.map +1 -0
- package/dist/SeedPhraseMetadata.d.mts +82 -0
- package/dist/SeedPhraseMetadata.d.mts.map +1 -0
- package/dist/SeedPhraseMetadata.mjs +142 -0
- package/dist/SeedPhraseMetadata.mjs.map +1 -0
- package/dist/SeedlessOnboardingController.cjs +578 -0
- package/dist/SeedlessOnboardingController.cjs.map +1 -0
- package/dist/SeedlessOnboardingController.d.cts +87 -0
- package/dist/SeedlessOnboardingController.d.cts.map +1 -0
- package/dist/SeedlessOnboardingController.d.mts +87 -0
- package/dist/SeedlessOnboardingController.d.mts.map +1 -0
- package/dist/SeedlessOnboardingController.mjs +573 -0
- package/dist/SeedlessOnboardingController.mjs.map +1 -0
- package/dist/constants.cjs +29 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.d.cts +24 -0
- package/dist/constants.d.cts.map +1 -0
- package/dist/constants.d.mts +24 -0
- package/dist/constants.d.mts.map +1 -0
- package/dist/constants.mjs +26 -0
- package/dist/constants.mjs.map +1 -0
- package/dist/errors.cjs +76 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.cts +26 -0
- package/dist/errors.d.cts.map +1 -0
- package/dist/errors.d.mts +26 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +72 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger.cjs +8 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.d.cts +5 -0
- package/dist/logger.d.cts.map +1 -0
- package/dist/logger.d.mts +5 -0
- package/dist/logger.d.mts.map +1 -0
- package/dist/logger.mjs +5 -0
- package/dist/logger.mjs.map +1 -0
- package/dist/types.cjs +3 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +120 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +120 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +2 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _SeedlessOnboardingController_instances, _SeedlessOnboardingController_vaultEncryptor, _SeedlessOnboardingController_vaultOperationMutex, _SeedlessOnboardingController_persistOprfKey, _SeedlessOnboardingController_recoverEncKey, _SeedlessOnboardingController_changeEncryptionKey, _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup, _SeedlessOnboardingController_unlockVaultWithPassword, _SeedlessOnboardingController_withPersistedSeedPhraseBackupsState, _SeedlessOnboardingController_createNewVaultWithAuthData, _SeedlessOnboardingController_updateVault, _SeedlessOnboardingController_withVaultLock, _SeedlessOnboardingController_serializeKeyData, _SeedlessOnboardingController_parseVaultData, _SeedlessOnboardingController_assertIsAuthenticatedUser, _SeedlessOnboardingController_assertIsValidVaultData;
|
|
13
|
+
import { keccak256AndHexify } from "@metamask/auth-network-utils";
|
|
14
|
+
import { BaseController } from "@metamask/base-controller";
|
|
15
|
+
import { encrypt, decrypt } from "@metamask/browser-passworder";
|
|
16
|
+
import { ToprfSecureBackup } from "@metamask/toprf-secure-backup";
|
|
17
|
+
import { base64ToBytes, bytesToBase64, stringToBytes, remove0x, bigIntToHex } from "@metamask/utils";
|
|
18
|
+
import { Mutex } from "async-mutex";
|
|
19
|
+
import { controllerName, SeedlessOnboardingControllerError, Web3AuthNetwork } from "./constants.mjs";
|
|
20
|
+
import { RecoveryError } from "./errors.mjs";
|
|
21
|
+
import { projectLogger, createModuleLogger } from "./logger.mjs";
|
|
22
|
+
import { SeedPhraseMetadata } from "./SeedPhraseMetadata.mjs";
|
|
23
|
+
const log = createModuleLogger(projectLogger, controllerName);
|
|
24
|
+
/**
|
|
25
|
+
* Get the default state for the Seedless Onboarding Controller.
|
|
26
|
+
*
|
|
27
|
+
* @returns The default state for the Seedless Onboarding Controller.
|
|
28
|
+
*/
|
|
29
|
+
export function getDefaultSeedlessOnboardingControllerState() {
|
|
30
|
+
return {
|
|
31
|
+
backupHashes: [],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Seedless Onboarding Controller State Metadata.
|
|
36
|
+
*
|
|
37
|
+
* This allows us to choose if fields of the state should be persisted or not
|
|
38
|
+
* using the `persist` flag; and if they can be sent to Sentry or not, using
|
|
39
|
+
* the `anonymous` flag.
|
|
40
|
+
*/
|
|
41
|
+
const seedlessOnboardingMetadata = {
|
|
42
|
+
vault: {
|
|
43
|
+
persist: true,
|
|
44
|
+
anonymous: false,
|
|
45
|
+
},
|
|
46
|
+
backupHashes: {
|
|
47
|
+
persist: true,
|
|
48
|
+
anonymous: true,
|
|
49
|
+
},
|
|
50
|
+
nodeAuthTokens: {
|
|
51
|
+
persist: false,
|
|
52
|
+
anonymous: true,
|
|
53
|
+
},
|
|
54
|
+
authConnectionId: {
|
|
55
|
+
persist: true,
|
|
56
|
+
anonymous: true,
|
|
57
|
+
},
|
|
58
|
+
groupedAuthConnectionId: {
|
|
59
|
+
persist: true,
|
|
60
|
+
anonymous: true,
|
|
61
|
+
},
|
|
62
|
+
userId: {
|
|
63
|
+
persist: true,
|
|
64
|
+
anonymous: true,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
export class SeedlessOnboardingController extends BaseController {
|
|
68
|
+
/**
|
|
69
|
+
* Creates a new SeedlessOnboardingController instance.
|
|
70
|
+
*
|
|
71
|
+
* @param options - The options for the SeedlessOnboardingController.
|
|
72
|
+
* @param options.messenger - A restricted messenger.
|
|
73
|
+
* @param options.state - Initial state to set on this controller.
|
|
74
|
+
* @param options.encryptor - An optional encryptor to use for encrypting and decrypting seedless onboarding vault.
|
|
75
|
+
* @param options.network - The network to be used for the Seedless Onboarding flow.
|
|
76
|
+
*/
|
|
77
|
+
constructor({ messenger, state, encryptor = { encrypt, decrypt }, // default to `encrypt` and `decrypt` from `@metamask/browser-passworder`
|
|
78
|
+
network = Web3AuthNetwork.Mainnet, }) {
|
|
79
|
+
super({
|
|
80
|
+
name: controllerName,
|
|
81
|
+
metadata: seedlessOnboardingMetadata,
|
|
82
|
+
state: {
|
|
83
|
+
...getDefaultSeedlessOnboardingControllerState(),
|
|
84
|
+
...state,
|
|
85
|
+
},
|
|
86
|
+
messenger,
|
|
87
|
+
});
|
|
88
|
+
_SeedlessOnboardingController_instances.add(this);
|
|
89
|
+
_SeedlessOnboardingController_vaultEncryptor.set(this, void 0);
|
|
90
|
+
_SeedlessOnboardingController_vaultOperationMutex.set(this, new Mutex());
|
|
91
|
+
__classPrivateFieldSet(this, _SeedlessOnboardingController_vaultEncryptor, encryptor, "f");
|
|
92
|
+
this.toprfClient = new ToprfSecureBackup({
|
|
93
|
+
network,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Authenticate OAuth user using the seedless onboarding flow
|
|
98
|
+
* and determine if the user is already registered or not.
|
|
99
|
+
*
|
|
100
|
+
* @param params - The parameters for authenticate OAuth user.
|
|
101
|
+
* @param params.idTokens - The ID token(s) issued by OAuth verification service. Currently this array only contains a single idToken which is verified by all the nodes, in future we are considering to issue a unique idToken for each node.
|
|
102
|
+
* @param params.authConnectionId - OAuth authConnectionId from dashboard
|
|
103
|
+
* @param params.userId - user email or id from Social login
|
|
104
|
+
* @param params.groupedAuthConnectionId - Optional grouped authConnectionId to be used for the authenticate request.
|
|
105
|
+
* You can pass this to use aggregate multiple OAuth connections. Useful when you want user to have same account while using different OAuth connections.
|
|
106
|
+
* @returns A promise that resolves to the authentication result.
|
|
107
|
+
*/
|
|
108
|
+
async authenticate(params) {
|
|
109
|
+
try {
|
|
110
|
+
const { idTokens, authConnectionId, groupedAuthConnectionId, userId } = params;
|
|
111
|
+
const hashedIdTokenHexes = idTokens.map((idToken) => {
|
|
112
|
+
return remove0x(keccak256AndHexify(stringToBytes(idToken)));
|
|
113
|
+
});
|
|
114
|
+
const authenticationResult = await this.toprfClient.authenticate({
|
|
115
|
+
verifier: groupedAuthConnectionId || authConnectionId,
|
|
116
|
+
verifierId: userId,
|
|
117
|
+
idTokens: hashedIdTokenHexes,
|
|
118
|
+
singleIdVerifierParams: {
|
|
119
|
+
subVerifier: authConnectionId,
|
|
120
|
+
subVerifierIdTokens: idTokens,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
// update the state with the authenticated user info
|
|
124
|
+
this.update((state) => {
|
|
125
|
+
state.nodeAuthTokens = authenticationResult.nodeAuthTokens;
|
|
126
|
+
state.authConnectionId = authConnectionId;
|
|
127
|
+
state.groupedAuthConnectionId = groupedAuthConnectionId;
|
|
128
|
+
state.userId = userId;
|
|
129
|
+
});
|
|
130
|
+
return authenticationResult;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
log('Error authenticating user', error);
|
|
134
|
+
throw new Error(SeedlessOnboardingControllerError.AuthenticationError);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Create a new TOPRF encryption key using given password and backups the provided seed phrase.
|
|
139
|
+
*
|
|
140
|
+
* @param password - The password used to create new wallet and seedphrase
|
|
141
|
+
* @param seedPhrase - The seed phrase to backup
|
|
142
|
+
* @returns A promise that resolves to the encrypted seed phrase and the encryption key.
|
|
143
|
+
*/
|
|
144
|
+
async createToprfKeyAndBackupSeedPhrase(password, seedPhrase) {
|
|
145
|
+
// to make sure that fail fast,
|
|
146
|
+
// assert that the user is authenticated before creating the TOPRF key and backing up the seed phrase
|
|
147
|
+
__classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
|
|
148
|
+
// locally evaluate the encryption key from the password
|
|
149
|
+
const { encKey, authKeyPair, oprfKey } = this.toprfClient.createLocalKey({
|
|
150
|
+
password,
|
|
151
|
+
});
|
|
152
|
+
// encrypt and store the seed phrase backup
|
|
153
|
+
await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup).call(this, seedPhrase, encKey, authKeyPair);
|
|
154
|
+
// store/persist the encryption key shares
|
|
155
|
+
// We store the seed phrase metadata in the metadata store first. If this operation fails,
|
|
156
|
+
// we avoid persisting the encryption key shares to prevent a situation where a user appears
|
|
157
|
+
// to have an account but with no associated data.
|
|
158
|
+
await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_persistOprfKey).call(this, oprfKey, authKeyPair.pk);
|
|
159
|
+
// create a new vault with the resulting authentication data
|
|
160
|
+
await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_createNewVaultWithAuthData).call(this, {
|
|
161
|
+
password,
|
|
162
|
+
rawToprfEncryptionKey: encKey,
|
|
163
|
+
rawToprfAuthKeyPair: authKeyPair,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Add a new seed phrase backup to the metadata store.
|
|
168
|
+
*
|
|
169
|
+
* @param seedPhrase - The seed phrase to backup.
|
|
170
|
+
* @param password - The password used to create new wallet and seedphrase
|
|
171
|
+
* @returns A promise that resolves to the success of the operation.
|
|
172
|
+
*/
|
|
173
|
+
async addNewSeedPhraseBackup(seedPhrase, password) {
|
|
174
|
+
// verify the password and unlock the vault
|
|
175
|
+
const { toprfEncryptionKey, toprfAuthKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_unlockVaultWithPassword).call(this, password);
|
|
176
|
+
// encrypt and store the seed phrase backup
|
|
177
|
+
await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup).call(this, seedPhrase, toprfEncryptionKey, toprfAuthKeyPair);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Fetches all encrypted seed phrases and metadata for user's account from the metadata store.
|
|
181
|
+
*
|
|
182
|
+
* Decrypts the seed phrases and returns the decrypted seed phrases using the recovered encryption key from the password.
|
|
183
|
+
*
|
|
184
|
+
* @param password - The password used to create new wallet and seedphrase
|
|
185
|
+
* @returns A promise that resolves to the seed phrase metadata.
|
|
186
|
+
*/
|
|
187
|
+
async fetchAllSeedPhrases(password) {
|
|
188
|
+
// assert that the user is authenticated before fetching the seed phrases
|
|
189
|
+
__classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
|
|
190
|
+
const { encKey, authKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverEncKey).call(this, password);
|
|
191
|
+
try {
|
|
192
|
+
const secretData = await this.toprfClient.fetchAllSecretDataItems({
|
|
193
|
+
decKey: encKey,
|
|
194
|
+
authKeyPair,
|
|
195
|
+
});
|
|
196
|
+
if (secretData?.length > 0) {
|
|
197
|
+
await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_createNewVaultWithAuthData).call(this, {
|
|
198
|
+
password,
|
|
199
|
+
rawToprfEncryptionKey: encKey,
|
|
200
|
+
rawToprfAuthKeyPair: authKeyPair,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withPersistedSeedPhraseBackupsState).call(this, () => Promise.resolve(SeedPhraseMetadata.parseSeedPhraseFromMetadataStore(secretData)));
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
log('Error fetching seed phrase metadata', error);
|
|
207
|
+
throw new Error(SeedlessOnboardingControllerError.FailedToFetchSeedPhraseMetadata);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Update the password of the seedless onboarding flow.
|
|
212
|
+
*
|
|
213
|
+
* Changing password will also update the encryption key, metadata store and the vault with new encrypted values.
|
|
214
|
+
*
|
|
215
|
+
* @param newPassword - The new password to update.
|
|
216
|
+
* @param oldPassword - The old password to verify.
|
|
217
|
+
*/
|
|
218
|
+
async changePassword(newPassword, oldPassword) {
|
|
219
|
+
// verify the old password of the encrypted vault
|
|
220
|
+
await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_unlockVaultWithPassword).call(this, oldPassword);
|
|
221
|
+
try {
|
|
222
|
+
// update the encryption key with new password and update the Metadata Store
|
|
223
|
+
const { encKey: newEncKey, authKeyPair: newAuthKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_changeEncryptionKey).call(this, newPassword, oldPassword);
|
|
224
|
+
// update and encrypt the vault with new password
|
|
225
|
+
await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_createNewVaultWithAuthData).call(this, {
|
|
226
|
+
password: newPassword,
|
|
227
|
+
rawToprfEncryptionKey: newEncKey,
|
|
228
|
+
rawToprfAuthKeyPair: newAuthKeyPair,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
log('Error changing password', error);
|
|
233
|
+
throw new Error(SeedlessOnboardingControllerError.FailedToChangePassword);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get the hash of the seed phrase backup for the given seed phrase, from the state.
|
|
238
|
+
*
|
|
239
|
+
* If the given seed phrase is not backed up and not found in the state, it will return `undefined`.
|
|
240
|
+
*
|
|
241
|
+
* @param seedPhrase - The seed phrase to get the hash of.
|
|
242
|
+
* @returns A promise that resolves to the hash of the seed phrase backup.
|
|
243
|
+
*/
|
|
244
|
+
getSeedPhraseBackupHash(seedPhrase) {
|
|
245
|
+
const seedPhraseHash = keccak256AndHexify(seedPhrase);
|
|
246
|
+
return this.state.backupHashes.find((hash) => hash === seedPhraseHash);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
_SeedlessOnboardingController_vaultEncryptor = new WeakMap(), _SeedlessOnboardingController_vaultOperationMutex = new WeakMap(), _SeedlessOnboardingController_instances = new WeakSet(), _SeedlessOnboardingController_persistOprfKey =
|
|
250
|
+
/**
|
|
251
|
+
* Persist the encryption key for the seedless onboarding flow.
|
|
252
|
+
*
|
|
253
|
+
* @param oprfKey - The OPRF key to be splited and persisted.
|
|
254
|
+
* @param authPubKey - The authentication public key.
|
|
255
|
+
* @returns A promise that resolves to the success of the operation.
|
|
256
|
+
*/
|
|
257
|
+
async function _SeedlessOnboardingController_persistOprfKey(oprfKey, authPubKey) {
|
|
258
|
+
__classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
|
|
259
|
+
const verifier = this.state.groupedAuthConnectionId || this.state.authConnectionId;
|
|
260
|
+
try {
|
|
261
|
+
await this.toprfClient.persistLocalKey({
|
|
262
|
+
nodeAuthTokens: this.state.nodeAuthTokens,
|
|
263
|
+
verifier,
|
|
264
|
+
verifierId: this.state.userId,
|
|
265
|
+
oprfKey,
|
|
266
|
+
authPubKey,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
log('Error persisting local encryption key', error);
|
|
271
|
+
throw new Error(SeedlessOnboardingControllerError.FailedToPersistOprfKey);
|
|
272
|
+
}
|
|
273
|
+
}, _SeedlessOnboardingController_recoverEncKey =
|
|
274
|
+
/**
|
|
275
|
+
* Recover the encryption key from password.
|
|
276
|
+
*
|
|
277
|
+
* @param password - The password used to derive/recover the encryption key.
|
|
278
|
+
* @returns A promise that resolves to the encryption key and authentication key pair.
|
|
279
|
+
* @throws RecoveryError - If failed to recover the encryption key.
|
|
280
|
+
*/
|
|
281
|
+
async function _SeedlessOnboardingController_recoverEncKey(password) {
|
|
282
|
+
__classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
|
|
283
|
+
const verifier = this.state.groupedAuthConnectionId || this.state.authConnectionId;
|
|
284
|
+
try {
|
|
285
|
+
const recoverEncKeyResult = await this.toprfClient.recoverEncKey({
|
|
286
|
+
nodeAuthTokens: this.state.nodeAuthTokens,
|
|
287
|
+
password,
|
|
288
|
+
verifier,
|
|
289
|
+
verifierId: this.state.userId,
|
|
290
|
+
});
|
|
291
|
+
return recoverEncKeyResult;
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
throw RecoveryError.getInstance(error);
|
|
295
|
+
}
|
|
296
|
+
}, _SeedlessOnboardingController_changeEncryptionKey =
|
|
297
|
+
/**
|
|
298
|
+
* Update the encryption key with new password and update the Metadata Store with new encryption key.
|
|
299
|
+
*
|
|
300
|
+
* @param newPassword - The new password to update.
|
|
301
|
+
* @param oldPassword - The old password to verify.
|
|
302
|
+
* @returns A promise that resolves to new encryption key and authentication key pair.
|
|
303
|
+
*/
|
|
304
|
+
async function _SeedlessOnboardingController_changeEncryptionKey(newPassword, oldPassword) {
|
|
305
|
+
__classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
|
|
306
|
+
const verifier = this.state.groupedAuthConnectionId || this.state.authConnectionId;
|
|
307
|
+
const { encKey, authKeyPair, keyShareIndex: newKeyShareIndex, } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverEncKey).call(this, oldPassword);
|
|
308
|
+
return await this.toprfClient.changeEncKey({
|
|
309
|
+
nodeAuthTokens: this.state.nodeAuthTokens,
|
|
310
|
+
verifier,
|
|
311
|
+
verifierId: this.state.userId,
|
|
312
|
+
oldEncKey: encKey,
|
|
313
|
+
oldAuthKeyPair: authKeyPair,
|
|
314
|
+
newKeyShareIndex,
|
|
315
|
+
newPassword,
|
|
316
|
+
});
|
|
317
|
+
}, _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup =
|
|
318
|
+
/**
|
|
319
|
+
* Encrypt and store the seed phrase backup in the metadata store.
|
|
320
|
+
*
|
|
321
|
+
* @param seedPhrase - The seed phrase to store.
|
|
322
|
+
* @param encKey - The encryption key to store.
|
|
323
|
+
* @param authKeyPair - The authentication key pair to store.
|
|
324
|
+
*
|
|
325
|
+
* @returns A promise that resolves to the success of the operation.
|
|
326
|
+
*/
|
|
327
|
+
async function _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup(seedPhrase, encKey, authKeyPair) {
|
|
328
|
+
try {
|
|
329
|
+
const seedPhraseMetadata = new SeedPhraseMetadata(seedPhrase);
|
|
330
|
+
const secretData = seedPhraseMetadata.toBytes();
|
|
331
|
+
await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withPersistedSeedPhraseBackupsState).call(this, async () => {
|
|
332
|
+
await this.toprfClient.addSecretDataItem({
|
|
333
|
+
encKey,
|
|
334
|
+
secretData,
|
|
335
|
+
authKeyPair,
|
|
336
|
+
});
|
|
337
|
+
return seedPhrase;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
log('Error encrypting and storing seed phrase backup', error);
|
|
342
|
+
throw new Error(SeedlessOnboardingControllerError.FailedToEncryptAndStoreSeedPhraseBackup);
|
|
343
|
+
}
|
|
344
|
+
}, _SeedlessOnboardingController_unlockVaultWithPassword =
|
|
345
|
+
/**
|
|
346
|
+
* Unlocks the encrypted vault using the provided password and returns the decrypted vault data.
|
|
347
|
+
* This method ensures thread-safety by using a mutex lock when accessing the vault.
|
|
348
|
+
*
|
|
349
|
+
* @param password - The password to decrypt the vault.
|
|
350
|
+
* @returns A promise that resolves to an object containing:
|
|
351
|
+
* - nodeAuthTokens: Authentication tokens to communicate with the TOPRF service
|
|
352
|
+
* - toprfEncryptionKey: The decrypted TOPRF encryption key
|
|
353
|
+
* - toprfAuthKeyPair: The decrypted TOPRF authentication key pair
|
|
354
|
+
* @throws {Error} If:
|
|
355
|
+
* - The password is invalid or empty
|
|
356
|
+
* - The vault is not initialized
|
|
357
|
+
* - The password is incorrect (from encryptor.decrypt)
|
|
358
|
+
* - The decrypted vault data is malformed
|
|
359
|
+
*/
|
|
360
|
+
async function _SeedlessOnboardingController_unlockVaultWithPassword(password) {
|
|
361
|
+
return __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withVaultLock).call(this, async () => {
|
|
362
|
+
assertIsValidPassword(password);
|
|
363
|
+
const encryptedVault = this.state.vault;
|
|
364
|
+
if (!encryptedVault) {
|
|
365
|
+
throw new Error(SeedlessOnboardingControllerError.VaultError);
|
|
366
|
+
}
|
|
367
|
+
// Note that vault decryption using the password is a very costly operation as it involves deriving the encryption key
|
|
368
|
+
// from the password using an intentionally slow key derivation function.
|
|
369
|
+
// We should make sure that we only call it very intentionally.
|
|
370
|
+
const decryptedVaultData = await __classPrivateFieldGet(this, _SeedlessOnboardingController_vaultEncryptor, "f").decrypt(password, encryptedVault);
|
|
371
|
+
const { nodeAuthTokens, toprfEncryptionKey, toprfAuthKeyPair } = __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_parseVaultData).call(this, decryptedVaultData);
|
|
372
|
+
// update the state with the restored nodeAuthTokens
|
|
373
|
+
this.update((state) => {
|
|
374
|
+
state.nodeAuthTokens = nodeAuthTokens;
|
|
375
|
+
});
|
|
376
|
+
return { nodeAuthTokens, toprfEncryptionKey, toprfAuthKeyPair };
|
|
377
|
+
});
|
|
378
|
+
}, _SeedlessOnboardingController_withPersistedSeedPhraseBackupsState =
|
|
379
|
+
/**
|
|
380
|
+
* Executes a callback function that creates or restores seed phrases and persists their hashes in the controller state.
|
|
381
|
+
*
|
|
382
|
+
* This method:
|
|
383
|
+
* 1. Executes the provided callback to create/restore seed phrases
|
|
384
|
+
* 2. Generates keccak256 hashes of the seed phrases
|
|
385
|
+
* 3. Merges new hashes with existing ones in the state, ensuring uniqueness
|
|
386
|
+
* 4. Updates the controller state with the combined hashes
|
|
387
|
+
*
|
|
388
|
+
* This is a wrapper method that should be used around any operation that creates
|
|
389
|
+
* or restores seed phrases to ensure their hashes are properly tracked.
|
|
390
|
+
*
|
|
391
|
+
* @param createOrRestoreSeedPhraseBackupCallback - function that returns either a single seed phrase
|
|
392
|
+
* or an array of seed phrases as Uint8Array(s)
|
|
393
|
+
* @returns The original seed phrase(s) returned by the callback
|
|
394
|
+
* @throws Rethrows any errors from the callback with additional logging
|
|
395
|
+
*/
|
|
396
|
+
async function _SeedlessOnboardingController_withPersistedSeedPhraseBackupsState(createOrRestoreSeedPhraseBackupCallback) {
|
|
397
|
+
try {
|
|
398
|
+
const backedUpSeedPhrases = await createOrRestoreSeedPhraseBackupCallback();
|
|
399
|
+
let backedUpHashB64Strings = [];
|
|
400
|
+
if (Array.isArray(backedUpSeedPhrases)) {
|
|
401
|
+
backedUpHashB64Strings = backedUpSeedPhrases.map((seedPhrase) => keccak256AndHexify(seedPhrase));
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
backedUpHashB64Strings = [keccak256AndHexify(backedUpSeedPhrases)];
|
|
405
|
+
}
|
|
406
|
+
const existingBackedUpHashes = this.state.backupHashes;
|
|
407
|
+
const uniqueHashesSet = new Set([
|
|
408
|
+
...existingBackedUpHashes,
|
|
409
|
+
...backedUpHashB64Strings,
|
|
410
|
+
]);
|
|
411
|
+
this.update((state) => {
|
|
412
|
+
state.backupHashes = Array.from(uniqueHashesSet);
|
|
413
|
+
});
|
|
414
|
+
return backedUpSeedPhrases;
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
log('Error persisting seed phrase backups', error);
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
}, _SeedlessOnboardingController_createNewVaultWithAuthData =
|
|
421
|
+
/**
|
|
422
|
+
* Create a new vault with the given authentication data.
|
|
423
|
+
*
|
|
424
|
+
* Serialize the authentication and key data which will be stored in the vault.
|
|
425
|
+
*
|
|
426
|
+
* @param params - The parameters for creating a new vault.
|
|
427
|
+
* @param params.password - The password to encrypt the vault.
|
|
428
|
+
* @param params.rawToprfEncryptionKey - The encryption key to encrypt the vault.
|
|
429
|
+
* @param params.rawToprfAuthKeyPair - The authentication key pair to encrypt the vault.
|
|
430
|
+
*/
|
|
431
|
+
async function _SeedlessOnboardingController_createNewVaultWithAuthData({ password, rawToprfEncryptionKey, rawToprfAuthKeyPair, }) {
|
|
432
|
+
__classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
|
|
433
|
+
const { toprfEncryptionKey, toprfAuthKeyPair } = __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_serializeKeyData).call(this, rawToprfEncryptionKey, rawToprfAuthKeyPair);
|
|
434
|
+
const serializedVaultData = JSON.stringify({
|
|
435
|
+
authTokens: this.state.nodeAuthTokens,
|
|
436
|
+
toprfEncryptionKey,
|
|
437
|
+
toprfAuthKeyPair,
|
|
438
|
+
});
|
|
439
|
+
await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_updateVault).call(this, {
|
|
440
|
+
password,
|
|
441
|
+
serializedVaultData,
|
|
442
|
+
});
|
|
443
|
+
}, _SeedlessOnboardingController_updateVault =
|
|
444
|
+
/**
|
|
445
|
+
* Encrypt and update the vault with the given authentication data.
|
|
446
|
+
*
|
|
447
|
+
* @param params - The parameters for updating the vault.
|
|
448
|
+
* @param params.password - The password to encrypt the vault.
|
|
449
|
+
* @param params.serializedVaultData - The serialized authentication data to update the vault with.
|
|
450
|
+
* @returns A promise that resolves to the updated vault.
|
|
451
|
+
*/
|
|
452
|
+
async function _SeedlessOnboardingController_updateVault({ password, serializedVaultData, }) {
|
|
453
|
+
await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withVaultLock).call(this, async () => {
|
|
454
|
+
assertIsValidPassword(password);
|
|
455
|
+
const updatedState = {};
|
|
456
|
+
// Note that vault encryption using the password is a very costly operation as it involves deriving the encryption key
|
|
457
|
+
// from the password using an intentionally slow key derivation function.
|
|
458
|
+
// We should make sure that we only call it very intentionally.
|
|
459
|
+
updatedState.vault = await __classPrivateFieldGet(this, _SeedlessOnboardingController_vaultEncryptor, "f").encrypt(password, serializedVaultData);
|
|
460
|
+
this.update((state) => {
|
|
461
|
+
state.vault = updatedState.vault;
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
}, _SeedlessOnboardingController_withVaultLock =
|
|
465
|
+
/**
|
|
466
|
+
* Lock the vault mutex before executing the given function,
|
|
467
|
+
* and release it after the function is resolved or after an
|
|
468
|
+
* error is thrown.
|
|
469
|
+
*
|
|
470
|
+
* This ensures that each operation that interacts with the vault
|
|
471
|
+
* is executed in a mutually exclusive way.
|
|
472
|
+
*
|
|
473
|
+
* @param callback - The function to execute while the vault mutex is locked.
|
|
474
|
+
* @returns The result of the function.
|
|
475
|
+
*/
|
|
476
|
+
async function _SeedlessOnboardingController_withVaultLock(callback) {
|
|
477
|
+
return await withLock(__classPrivateFieldGet(this, _SeedlessOnboardingController_vaultOperationMutex, "f"), callback);
|
|
478
|
+
}, _SeedlessOnboardingController_serializeKeyData = function _SeedlessOnboardingController_serializeKeyData(encKey, authKeyPair) {
|
|
479
|
+
const b64EncodedEncKey = bytesToBase64(encKey);
|
|
480
|
+
const b64EncodedAuthKeyPair = JSON.stringify({
|
|
481
|
+
sk: bigIntToHex(authKeyPair.sk),
|
|
482
|
+
pk: bytesToBase64(authKeyPair.pk),
|
|
483
|
+
});
|
|
484
|
+
return {
|
|
485
|
+
toprfEncryptionKey: b64EncodedEncKey,
|
|
486
|
+
toprfAuthKeyPair: b64EncodedAuthKeyPair,
|
|
487
|
+
};
|
|
488
|
+
}, _SeedlessOnboardingController_parseVaultData = function _SeedlessOnboardingController_parseVaultData(data) {
|
|
489
|
+
if (typeof data !== 'string') {
|
|
490
|
+
throw new Error(SeedlessOnboardingControllerError.InvalidVaultData);
|
|
491
|
+
}
|
|
492
|
+
let parsedVaultData;
|
|
493
|
+
try {
|
|
494
|
+
parsedVaultData = JSON.parse(data);
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
throw new Error(SeedlessOnboardingControllerError.InvalidVaultData);
|
|
498
|
+
}
|
|
499
|
+
__classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsValidVaultData).call(this, parsedVaultData);
|
|
500
|
+
const rawToprfEncryptionKey = base64ToBytes(parsedVaultData.toprfEncryptionKey);
|
|
501
|
+
const parsedToprfAuthKeyPair = JSON.parse(parsedVaultData.toprfAuthKeyPair);
|
|
502
|
+
const rawToprfAuthKeyPair = {
|
|
503
|
+
sk: BigInt(parsedToprfAuthKeyPair.sk),
|
|
504
|
+
pk: base64ToBytes(parsedToprfAuthKeyPair.pk),
|
|
505
|
+
};
|
|
506
|
+
return {
|
|
507
|
+
nodeAuthTokens: parsedVaultData.authTokens,
|
|
508
|
+
toprfEncryptionKey: rawToprfEncryptionKey,
|
|
509
|
+
toprfAuthKeyPair: rawToprfAuthKeyPair,
|
|
510
|
+
};
|
|
511
|
+
}, _SeedlessOnboardingController_assertIsAuthenticatedUser = function _SeedlessOnboardingController_assertIsAuthenticatedUser(value) {
|
|
512
|
+
if (!value ||
|
|
513
|
+
typeof value !== 'object' ||
|
|
514
|
+
!('authConnectionId' in value) ||
|
|
515
|
+
typeof value.authConnectionId !== 'string' ||
|
|
516
|
+
!('userId' in value) ||
|
|
517
|
+
typeof value.userId !== 'string') {
|
|
518
|
+
throw new Error(SeedlessOnboardingControllerError.MissingAuthUserInfo);
|
|
519
|
+
}
|
|
520
|
+
if (!('nodeAuthTokens' in value) ||
|
|
521
|
+
typeof value.nodeAuthTokens !== 'object' ||
|
|
522
|
+
!Array.isArray(value.nodeAuthTokens) ||
|
|
523
|
+
value.nodeAuthTokens.length < 3 // At least 3 auth tokens are required for Threshold OPRF service
|
|
524
|
+
) {
|
|
525
|
+
throw new Error(SeedlessOnboardingControllerError.InsufficientAuthToken);
|
|
526
|
+
}
|
|
527
|
+
}, _SeedlessOnboardingController_assertIsValidVaultData = function _SeedlessOnboardingController_assertIsValidVaultData(value) {
|
|
528
|
+
// value is not valid vault data if any of the following conditions are true:
|
|
529
|
+
if (!value || // value is not defined
|
|
530
|
+
typeof value !== 'object' || // value is not an object
|
|
531
|
+
!('authTokens' in value) || // authTokens is not defined
|
|
532
|
+
typeof value.authTokens !== 'object' || // authTokens is not an object
|
|
533
|
+
!('toprfEncryptionKey' in value) || // toprfEncryptionKey is not defined
|
|
534
|
+
typeof value.toprfEncryptionKey !== 'string' || // toprfEncryptionKey is not a string
|
|
535
|
+
!('toprfAuthKeyPair' in value) || // toprfAuthKeyPair is not defined
|
|
536
|
+
typeof value.toprfAuthKeyPair !== 'string' // toprfAuthKeyPair is not a string
|
|
537
|
+
) {
|
|
538
|
+
throw new Error(SeedlessOnboardingControllerError.VaultDataError);
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
/**
|
|
542
|
+
* Assert that the provided password is a valid non-empty string.
|
|
543
|
+
*
|
|
544
|
+
* @param password - The password to check.
|
|
545
|
+
* @throws If the password is not a valid string.
|
|
546
|
+
*/
|
|
547
|
+
function assertIsValidPassword(password) {
|
|
548
|
+
if (typeof password !== 'string') {
|
|
549
|
+
throw new Error(SeedlessOnboardingControllerError.WrongPasswordType);
|
|
550
|
+
}
|
|
551
|
+
if (!password || !password.length) {
|
|
552
|
+
throw new Error(SeedlessOnboardingControllerError.InvalidEmptyPassword);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Lock the given mutex before executing the given function,
|
|
557
|
+
* and release it after the function is resolved or after an
|
|
558
|
+
* error is thrown.
|
|
559
|
+
*
|
|
560
|
+
* @param mutex - The mutex to lock.
|
|
561
|
+
* @param callback - The function to execute while the mutex is locked.
|
|
562
|
+
* @returns The result of the function.
|
|
563
|
+
*/
|
|
564
|
+
async function withLock(mutex, callback) {
|
|
565
|
+
const releaseLock = await mutex.acquire();
|
|
566
|
+
try {
|
|
567
|
+
return await callback({ releaseLock });
|
|
568
|
+
}
|
|
569
|
+
finally {
|
|
570
|
+
releaseLock();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
//# sourceMappingURL=SeedlessOnboardingController.mjs.map
|