@dynamic-labs-wallet/browser 0.0.0-preview.112
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/index.cjs.d.ts +1 -0
- package/index.cjs.js +1792 -0
- package/index.esm.d.ts +1 -0
- package/index.esm.js +1725 -0
- package/internal/core/bip340.d.ts +22 -0
- package/internal/core/bip340.js +1 -0
- package/internal/core/common.d.ts +1 -0
- package/internal/core/common.js +1 -0
- package/internal/core/ecdsa.d.ts +23 -0
- package/internal/core/ecdsa.js +1 -0
- package/internal/core/ed25519.d.ts +22 -0
- package/internal/core/ed25519.js +1 -0
- package/internal/core/index.d.ts +6 -0
- package/internal/core/index.js +1 -0
- package/internal/core/native.d.ts +103 -0
- package/internal/core/native.js +1 -0
- package/internal/core/types.d.ts +58 -0
- package/internal/core/types.js +1 -0
- package/internal/web/generated/libmpc_executor.d.ts +130 -0
- package/internal/web/generated/libmpc_executor.js +2 -0
- package/internal/web/generated/libmpc_executor_bg.wasm +0 -0
- package/internal/web/generated/libmpc_executor_bg.wasm.d.ts +64 -0
- package/internal/web/index.d.ts +11 -0
- package/internal/web/index.js +1 -0
- package/package.json +34 -0
- package/src/backup/encryption.d.ts +22 -0
- package/src/backup/encryption.d.ts.map +1 -0
- package/src/backup/providers/googleDrive.d.ts +19 -0
- package/src/backup/providers/googleDrive.d.ts.map +1 -0
- package/src/client.d.ts +371 -0
- package/src/client.d.ts.map +1 -0
- package/src/constants.d.ts +5 -0
- package/src/constants.d.ts.map +1 -0
- package/src/index.d.ts +6 -0
- package/src/index.d.ts.map +1 -0
- package/src/mpc/index.d.ts +5 -0
- package/src/mpc/index.d.ts.map +1 -0
- package/src/mpc/mpc.d.ts +11 -0
- package/src/mpc/mpc.d.ts.map +1 -0
- package/src/mpc/types.d.ts +5 -0
- package/src/mpc/types.d.ts.map +1 -0
- package/src/services/iframeDisplay.d.ts +9 -0
- package/src/services/iframeDisplay.d.ts.map +1 -0
- package/src/services/iframeLocalStorage.d.ts +13 -0
- package/src/services/iframeLocalStorage.d.ts.map +1 -0
- package/src/services/localStorage.d.ts +32 -0
- package/src/services/localStorage.d.ts.map +1 -0
- package/src/services/logger.d.ts +3 -0
- package/src/services/logger.d.ts.map +1 -0
- package/src/services/messageTransportBridge.d.ts +3 -0
- package/src/services/messageTransportBridge.d.ts.map +1 -0
- package/src/types.d.ts +14 -0
- package/src/types.d.ts.map +1 -0
- package/src/utils.d.ts +43 -0
- package/src/utils.d.ts.map +1 -0
package/index.esm.js
ADDED
|
@@ -0,0 +1,1725 @@
|
|
|
1
|
+
import { SigningAlgorithm, MPC_RELAY_PROD_API_URL, getMPCChainConfig, BackupLocation, getClientThreshold, MPC_CONFIG, getTSSConfig, WalletOperation, getReshareConfig, DynamicApiClient, IFRAME_DOMAIN_PREPROD } from '@dynamic-labs-wallet/core';
|
|
2
|
+
export * from '@dynamic-labs-wallet/core';
|
|
3
|
+
import { BIP340, Ed25519, Ecdsa, MessageHash, EcdsaKeygenResult, Ed25519KeygenResult, BIP340KeygenResult } from './internal/web';
|
|
4
|
+
export { BIP340, BIP340InitKeygenResult, BIP340KeygenResult, Ecdsa, EcdsaInitKeygenResult, EcdsaKeygenResult, EcdsaPublicKey, EcdsaSignature, Ed25519, Ed25519InitKeygenResult, Ed25519KeygenResult, MessageHash } from './internal/web';
|
|
5
|
+
import { Logger } from '@dynamic-labs/logger';
|
|
6
|
+
import { createRequestChannel, parseMessageTransportData, applyDefaultMessageOrigin, createMessageTransport } from '@dynamic-labs/message-transport';
|
|
7
|
+
|
|
8
|
+
function _extends() {
|
|
9
|
+
_extends = Object.assign || function assign(target) {
|
|
10
|
+
for(var i = 1; i < arguments.length; i++){
|
|
11
|
+
var source = arguments[i];
|
|
12
|
+
for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
|
|
13
|
+
}
|
|
14
|
+
return target;
|
|
15
|
+
};
|
|
16
|
+
return _extends.apply(this, arguments);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD_API_URL })=>{
|
|
20
|
+
switch(signingAlgorithm){
|
|
21
|
+
case SigningAlgorithm.ECDSA:
|
|
22
|
+
return new Ecdsa(baseRelayUrl);
|
|
23
|
+
case SigningAlgorithm.ED25519:
|
|
24
|
+
return new Ed25519(baseRelayUrl);
|
|
25
|
+
case SigningAlgorithm.BIP340:
|
|
26
|
+
return new BIP340(baseRelayUrl);
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(`Unsupported signing algorithm: ${signingAlgorithm}`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const getMPCSigner = ({ chainName, baseRelayUrl })=>{
|
|
32
|
+
const chainConfig = getMPCChainConfig(chainName);
|
|
33
|
+
const signatureScheme = getMPCSignatureScheme({
|
|
34
|
+
signingAlgorithm: chainConfig.signingAlgorithm,
|
|
35
|
+
baseRelayUrl
|
|
36
|
+
});
|
|
37
|
+
return signatureScheme;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const DEFAULT_LOG_LEVEL = 'INFO';
|
|
41
|
+
const STORAGE_KEY = 'dynamic-waas-wallet-client';
|
|
42
|
+
const CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX = 'dynamicWalletKeyShareBackup';
|
|
43
|
+
|
|
44
|
+
const logger = new Logger('DynamicWaasWalletClient');
|
|
45
|
+
|
|
46
|
+
const bytesToBase64 = (arr)=>{
|
|
47
|
+
return btoa(Array.from(arr, (b)=>String.fromCharCode(b)).join(''));
|
|
48
|
+
};
|
|
49
|
+
const stringToBytes = (str)=>{
|
|
50
|
+
return new TextEncoder().encode(str);
|
|
51
|
+
};
|
|
52
|
+
const base64ToBytes = (base64)=>{
|
|
53
|
+
return new Uint8Array(Buffer.from(base64, 'base64'));
|
|
54
|
+
};
|
|
55
|
+
// Helper function to ensure proper base64 padding
|
|
56
|
+
const ensureBase64Padding = (str)=>{
|
|
57
|
+
return str.padEnd(Math.ceil(str.length / 4) * 4, '=');
|
|
58
|
+
};
|
|
59
|
+
const isBrowser = ()=>typeof window !== 'undefined';
|
|
60
|
+
const isHexString = (str)=>{
|
|
61
|
+
// Remove 0x prefix if present
|
|
62
|
+
const hex = str.startsWith('0x') ? str.slice(2) : str;
|
|
63
|
+
// Check if string contains only hex characters
|
|
64
|
+
return /^[0-9A-Fa-f]+$/.test(hex);
|
|
65
|
+
};
|
|
66
|
+
const getClientKeyShareExportFileName = ({ thresholdSignatureScheme, accountAddress })=>{
|
|
67
|
+
return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}.json`;
|
|
68
|
+
};
|
|
69
|
+
const getClientKeyShareBackupInfo = (params)=>{
|
|
70
|
+
var _params_walletProperties, _params_walletProperties_keyShares_;
|
|
71
|
+
const backups = {
|
|
72
|
+
[BackupLocation.DYNAMIC]: [],
|
|
73
|
+
[BackupLocation.GOOGLE_DRIVE]: [],
|
|
74
|
+
[BackupLocation.ICLOUD]: [],
|
|
75
|
+
[BackupLocation.USER]: [],
|
|
76
|
+
[BackupLocation.EXTERNAL]: []
|
|
77
|
+
};
|
|
78
|
+
if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
|
|
79
|
+
return {
|
|
80
|
+
backups,
|
|
81
|
+
passwordEncrypted: false
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
params.walletProperties.keyShares.forEach((keyShare)=>{
|
|
85
|
+
if (backups[keyShare.backupLocation]) {
|
|
86
|
+
backups[keyShare.backupLocation].push(keyShare.id);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const passwordEncrypted = Boolean((_params_walletProperties_keyShares_ = params.walletProperties.keyShares[0]) == null ? void 0 : _params_walletProperties_keyShares_.passwordEncrypted);
|
|
90
|
+
return {
|
|
91
|
+
backups,
|
|
92
|
+
passwordEncrypted
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Helper function to merge keyshares and remove duplicates based on pubkey and secretShare
|
|
97
|
+
* @param existingKeyShares - Array of existing keyshares
|
|
98
|
+
* @param newKeyShares - Array of new keyshares to merge
|
|
99
|
+
* @returns Array of merged unique keyshares
|
|
100
|
+
*/ const mergeUniqueKeyShares = (existingKeyShares, newKeyShares)=>{
|
|
101
|
+
const uniqueKeyShares = newKeyShares.filter((newShare)=>!existingKeyShares.some((existingShare)=>{
|
|
102
|
+
if (!(newShare == null ? void 0 : newShare.pubkey) || !(existingShare == null ? void 0 : existingShare.pubkey)) return false;
|
|
103
|
+
return newShare.pubkey.toString() === existingShare.pubkey.toString() && newShare.secretShare === existingShare.secretShare;
|
|
104
|
+
}));
|
|
105
|
+
return [
|
|
106
|
+
...existingKeyShares,
|
|
107
|
+
...uniqueKeyShares
|
|
108
|
+
];
|
|
109
|
+
};
|
|
110
|
+
const timeoutPromise = ({ timeInMs, activity = 'Ceremony' })=>{
|
|
111
|
+
return new Promise((_, reject)=>setTimeout(()=>reject(new Error(`${activity} did not complete in ${timeInMs}ms`)), timeInMs));
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Generic helper function to retry a promise-based operations
|
|
115
|
+
*
|
|
116
|
+
* @param operation - The async operation to retry
|
|
117
|
+
* @param config - Configuration options for retry behavior
|
|
118
|
+
* @returns Promise with the operation result
|
|
119
|
+
* @throws Last error encountered after all retries are exhausted
|
|
120
|
+
*/ async function retryPromise(operation, { maxAttempts = 5, retryInterval = 500, operationName = 'operation', logContext = {} } = {}) {
|
|
121
|
+
let attempts = 0;
|
|
122
|
+
while(attempts < maxAttempts){
|
|
123
|
+
try {
|
|
124
|
+
return await operation();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
attempts++;
|
|
127
|
+
if (attempts === maxAttempts) {
|
|
128
|
+
logger.error(`Failed to execute ${operationName} after ${maxAttempts} attempts`, _extends({}, logContext, {
|
|
129
|
+
error
|
|
130
|
+
}));
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
await new Promise((resolve)=>setTimeout(resolve, retryInterval));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// TypeScript needs this even though it's unreachable
|
|
137
|
+
throw new Error('Unreachable code');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const PBKDF2_ALGORITHM = 'PBKDF2';
|
|
141
|
+
const PBKDF2_ITERATIONS = 100000;
|
|
142
|
+
const PBKDF2_HASH_ALGORITHM = 'SHA-256';
|
|
143
|
+
const AES_GCM_ALGORITHM = 'AES-GCM';
|
|
144
|
+
const AES_GCM_LENGTH = 256;
|
|
145
|
+
const getKey = async ({ password, salt })=>{
|
|
146
|
+
const passwordBytes = stringToBytes(password);
|
|
147
|
+
const initialKey = await crypto.subtle.importKey('raw', passwordBytes, {
|
|
148
|
+
name: 'PBKDF2'
|
|
149
|
+
}, false, [
|
|
150
|
+
'deriveKey'
|
|
151
|
+
]);
|
|
152
|
+
return crypto.subtle.deriveKey({
|
|
153
|
+
name: PBKDF2_ALGORITHM,
|
|
154
|
+
salt,
|
|
155
|
+
iterations: PBKDF2_ITERATIONS,
|
|
156
|
+
hash: PBKDF2_HASH_ALGORITHM
|
|
157
|
+
}, initialKey, {
|
|
158
|
+
name: AES_GCM_ALGORITHM,
|
|
159
|
+
length: AES_GCM_LENGTH
|
|
160
|
+
}, false, [
|
|
161
|
+
'encrypt',
|
|
162
|
+
'decrypt'
|
|
163
|
+
]);
|
|
164
|
+
};
|
|
165
|
+
const encryptData = async ({ data, password })=>{
|
|
166
|
+
try {
|
|
167
|
+
// Generate a random salt and IV
|
|
168
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
169
|
+
const iv = crypto.getRandomValues(new Uint8Array(12)); // AES-GCM requires 12 bytes
|
|
170
|
+
const key = await getKey({
|
|
171
|
+
password,
|
|
172
|
+
salt
|
|
173
|
+
});
|
|
174
|
+
// Convert the input string to bytes
|
|
175
|
+
const dataBytes = new TextEncoder().encode(data);
|
|
176
|
+
// Encrypt the data
|
|
177
|
+
const encryptedData = await crypto.subtle.encrypt({
|
|
178
|
+
name: AES_GCM_ALGORITHM,
|
|
179
|
+
iv
|
|
180
|
+
}, key, dataBytes);
|
|
181
|
+
// Convert to base64 strings, ensure proper padding
|
|
182
|
+
return {
|
|
183
|
+
salt: bytesToBase64(salt),
|
|
184
|
+
iv: bytesToBase64(iv),
|
|
185
|
+
cipher: bytesToBase64(new Uint8Array(encryptedData))
|
|
186
|
+
};
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw new Error('Error encrypting data');
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const decryptData = async ({ data, password })=>{
|
|
192
|
+
try {
|
|
193
|
+
const { salt, iv, cipher } = data;
|
|
194
|
+
// Ensure proper base64 padding for all values
|
|
195
|
+
const paddedSalt = ensureBase64Padding(salt);
|
|
196
|
+
const paddedIv = ensureBase64Padding(iv);
|
|
197
|
+
const paddedCipher = ensureBase64Padding(cipher);
|
|
198
|
+
const saltBytes = base64ToBytes(paddedSalt);
|
|
199
|
+
const ivBytes = base64ToBytes(paddedIv);
|
|
200
|
+
const cipherBytes = base64ToBytes(paddedCipher);
|
|
201
|
+
const key = await getKey({
|
|
202
|
+
password,
|
|
203
|
+
salt: saltBytes
|
|
204
|
+
});
|
|
205
|
+
const decryptedData = await crypto.subtle.decrypt({
|
|
206
|
+
name: AES_GCM_ALGORITHM,
|
|
207
|
+
iv: ivBytes
|
|
208
|
+
}, key, cipherBytes);
|
|
209
|
+
return new TextDecoder().decode(decryptedData);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
throw new Error('Decryption failed');
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const GOOGLE_DRIVE_UPLOAD_API = 'https://www.googleapis.com';
|
|
216
|
+
const uploadFileToGoogleDriveAppStorage = async ({ accessToken, fileName, jsonData })=>{
|
|
217
|
+
return uploadFileToGoogleDrive({
|
|
218
|
+
accessToken,
|
|
219
|
+
fileName,
|
|
220
|
+
jsonData,
|
|
221
|
+
parents: [
|
|
222
|
+
'appDataFolder'
|
|
223
|
+
]
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
const uploadFileToGoogleDrivePersonal = async ({ accessToken, fileName, jsonData })=>{
|
|
227
|
+
return uploadFileToGoogleDrive({
|
|
228
|
+
accessToken,
|
|
229
|
+
fileName,
|
|
230
|
+
jsonData,
|
|
231
|
+
parents: [
|
|
232
|
+
'root'
|
|
233
|
+
]
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
const uploadFileToGoogleDrive = async ({ accessToken, fileName, jsonData, parents })=>{
|
|
237
|
+
const metadata = {
|
|
238
|
+
name: fileName,
|
|
239
|
+
mimeType: 'application/json',
|
|
240
|
+
parents
|
|
241
|
+
};
|
|
242
|
+
const form = new FormData();
|
|
243
|
+
form.append('metadata', new Blob([
|
|
244
|
+
JSON.stringify(metadata)
|
|
245
|
+
], {
|
|
246
|
+
type: 'application/json'
|
|
247
|
+
}));
|
|
248
|
+
form.append('file', new Blob([
|
|
249
|
+
JSON.stringify(jsonData)
|
|
250
|
+
], {
|
|
251
|
+
type: 'application/json'
|
|
252
|
+
}));
|
|
253
|
+
const response = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/upload/drive/v3/files?uploadType=multipart`, {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: {
|
|
256
|
+
Authorization: `Bearer ${accessToken}`
|
|
257
|
+
},
|
|
258
|
+
body: form
|
|
259
|
+
});
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
throw new Error('Error uploading file');
|
|
262
|
+
}
|
|
263
|
+
const result = await response.json();
|
|
264
|
+
return result; // Return file metadata, including file ID
|
|
265
|
+
};
|
|
266
|
+
const listFilesFromGoogleDrive = async ({ accessToken, name })=>{
|
|
267
|
+
// Step 1: List all files inside `appDataFolder` with the specified backup filename
|
|
268
|
+
const resp = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files?q=${encodeURIComponent(`name='${name}'`)}&spaces=appDataFolder&orderBy=createdTime desc`, {
|
|
269
|
+
headers: {
|
|
270
|
+
Authorization: `Bearer ${accessToken}`
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
const data = await resp.json();
|
|
274
|
+
// If no files found, return null
|
|
275
|
+
if (!data.files || data.files.length === 0) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
const files = data.files;
|
|
279
|
+
return files;
|
|
280
|
+
};
|
|
281
|
+
const downloadFileFromGoogleDrive = async ({ accessToken, name })=>{
|
|
282
|
+
const files = await listFilesFromGoogleDrive({
|
|
283
|
+
accessToken,
|
|
284
|
+
name
|
|
285
|
+
});
|
|
286
|
+
if (!files || files.length === 0) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
// Get the most recent file
|
|
290
|
+
const fileMetadata = files[0];
|
|
291
|
+
// Fetch the file data using the file ID
|
|
292
|
+
const fileRes = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files/${fileMetadata.id}?alt=media`, {
|
|
293
|
+
headers: {
|
|
294
|
+
Authorization: `Bearer ${accessToken}`
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
// Read the file's raw data
|
|
298
|
+
const fileRawData = await fileRes.text();
|
|
299
|
+
if (fileRawData.length === 0) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
// Just parse and return the data without validation
|
|
304
|
+
// The client will handle validation of the structure
|
|
305
|
+
return JSON.parse(fileRawData);
|
|
306
|
+
} catch (error) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const localStorageWriteTest = {
|
|
312
|
+
tested: false,
|
|
313
|
+
writable: false
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* Checks whether localStorage is supported on this browser.
|
|
317
|
+
*/ const supportsLocalStorage = ()=>{
|
|
318
|
+
if (!isBrowser()) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
if (typeof globalThis.localStorage !== 'object') {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
} catch (e) {
|
|
326
|
+
// DOM exception when accessing `localStorage`
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
if (localStorageWriteTest.tested) {
|
|
330
|
+
return localStorageWriteTest.writable;
|
|
331
|
+
}
|
|
332
|
+
const randomKey = `lswt-${Math.random()}${Math.random()}`;
|
|
333
|
+
try {
|
|
334
|
+
globalThis.localStorage.setItem(randomKey, randomKey);
|
|
335
|
+
globalThis.localStorage.removeItem(randomKey);
|
|
336
|
+
localStorageWriteTest.tested = true;
|
|
337
|
+
localStorageWriteTest.writable = true;
|
|
338
|
+
} catch (e) {
|
|
339
|
+
// localStorage can't be written to
|
|
340
|
+
localStorageWriteTest.tested = true;
|
|
341
|
+
localStorageWriteTest.writable = false;
|
|
342
|
+
}
|
|
343
|
+
return localStorageWriteTest.writable;
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* Provides safe access to the globalThis.localStorage property.
|
|
347
|
+
*/ const localStorageAdapter = {
|
|
348
|
+
getItem: (key)=>{
|
|
349
|
+
if (!supportsLocalStorage()) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
return globalThis.localStorage.getItem(key);
|
|
353
|
+
},
|
|
354
|
+
removeItem: (key)=>{
|
|
355
|
+
if (!supportsLocalStorage()) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
globalThis.localStorage.removeItem(key);
|
|
359
|
+
},
|
|
360
|
+
setItem: (key, value)=>{
|
|
361
|
+
if (!supportsLocalStorage()) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
globalThis.localStorage.setItem(key, value);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* Returns a localStorage-like object that stores the key-value pairs in
|
|
369
|
+
* memory.
|
|
370
|
+
*/ const memoryLocalStorageAdapter = (store = {})=>({
|
|
371
|
+
getItem: (key)=>store[key] || null,
|
|
372
|
+
removeItem: (key)=>{
|
|
373
|
+
delete store[key];
|
|
374
|
+
},
|
|
375
|
+
setItem: (key, value)=>{
|
|
376
|
+
store[key] = value;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* StorageRequestChannelAdapter.getItem() sends a message within host domain
|
|
382
|
+
* the bridge on host will capture this request and forwards it to the iframe via contentWindow.postMessage()
|
|
383
|
+
*/ class StorageRequestChannelAdapter {
|
|
384
|
+
async getItem(key) {
|
|
385
|
+
const item = await this.requestChannel.request('getItem', {
|
|
386
|
+
source: 'localStorage',
|
|
387
|
+
key
|
|
388
|
+
});
|
|
389
|
+
return item ? JSON.parse(item) : null;
|
|
390
|
+
}
|
|
391
|
+
async setItem(key, value) {
|
|
392
|
+
const stringifiedValue = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
393
|
+
return this.requestChannel.request('setItem', {
|
|
394
|
+
source: 'localStorage',
|
|
395
|
+
key,
|
|
396
|
+
data: stringifiedValue
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
async removeItem(key) {
|
|
400
|
+
return this.requestChannel.request('deleteItem', {
|
|
401
|
+
source: 'localStorage',
|
|
402
|
+
key
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
constructor(messageTransport){
|
|
406
|
+
this.requestChannel = createRequestChannel(messageTransport);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const setupMessageTransportBridge = (messageTransport, iframe, iframeOrigin)=>{
|
|
411
|
+
if (!(iframe == null ? void 0 : iframe.contentWindow)) {
|
|
412
|
+
throw new Error('Iframe or contentWindow not available');
|
|
413
|
+
}
|
|
414
|
+
const logger = new Logger('debug');
|
|
415
|
+
messageTransport.on((message)=>{
|
|
416
|
+
// Forward the message to webview via postMessage
|
|
417
|
+
if (message.origin === 'host') {
|
|
418
|
+
var _iframe_contentWindow;
|
|
419
|
+
logger.debug(`[host bridge] host --> bridge --> iframe`, message);
|
|
420
|
+
iframe == null ? void 0 : (_iframe_contentWindow = iframe.contentWindow) == null ? void 0 : _iframe_contentWindow.postMessage(message, iframeOrigin);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
const handleIncomingMessage = (message)=>{
|
|
424
|
+
const { data } = message;
|
|
425
|
+
if (!data) return;
|
|
426
|
+
if ((data == null ? void 0 : data.origin) !== 'webview') {
|
|
427
|
+
logger.debug(`skipped message: ${JSON.stringify(data)}: origin is not host`);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (typeof data !== 'object') {
|
|
431
|
+
logger.debug(`skipped message: ${JSON.stringify(data)}: data is not an object`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
const message = parseMessageTransportData(data);
|
|
436
|
+
logger.debug(`[host bridge] iframe --> bridge --> host`, message);
|
|
437
|
+
messageTransport.emit(message);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
if (!(error instanceof SyntaxError)) {
|
|
440
|
+
logger.error('Error handling incoming message:', error);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
/**
|
|
445
|
+
* Handle incoming message from android client
|
|
446
|
+
*/ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
447
|
+
// @ts-ignore
|
|
448
|
+
document.addEventListener('message', handleIncomingMessage);
|
|
449
|
+
/**
|
|
450
|
+
* Handle incoming message from iOS client
|
|
451
|
+
*/ window.addEventListener('message', handleIncomingMessage);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
class IframeDisplayChannelAdapter {
|
|
455
|
+
async displayClientShares(accountAddress) {
|
|
456
|
+
await this.requestChannel.request('displayClientShares', accountAddress);
|
|
457
|
+
}
|
|
458
|
+
async displayPrivateKey(accountAddress, privateKey) {
|
|
459
|
+
await this.requestChannel.request('displayPrivateKey', accountAddress, privateKey);
|
|
460
|
+
}
|
|
461
|
+
constructor(messageTransport){
|
|
462
|
+
this.requestChannel = createRequestChannel(messageTransport);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
class DynamicWalletClient {
|
|
467
|
+
async initialize() {
|
|
468
|
+
if (this.initializePromise) {
|
|
469
|
+
return await this.initializePromise;
|
|
470
|
+
}
|
|
471
|
+
this.logger.debug('Initializing Dynamic Waas Wallet SDK');
|
|
472
|
+
this.initializePromise = this._initialize();
|
|
473
|
+
const result = await this.initializePromise;
|
|
474
|
+
this.logger.debug('Dynamic Waas Wallet SDK initialized');
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Initializes the iframe display for a specific container.
|
|
479
|
+
*
|
|
480
|
+
* @param {HTMLElement} container - The container to which the iframe will be attached.
|
|
481
|
+
* @returns {Promise<{
|
|
482
|
+
* iframe: HTMLIFrameElement;
|
|
483
|
+
* iframeDisplay: IframeDisplayChannelAdapter;
|
|
484
|
+
* cleanup: () => void;
|
|
485
|
+
* }>} A promise that resolves when the iframe is loaded.
|
|
486
|
+
*/ async initializeIframeDisplayForContainer({ container }) {
|
|
487
|
+
try {
|
|
488
|
+
const iframe = await this.loadIframeForContainer(container);
|
|
489
|
+
const transport = applyDefaultMessageOrigin({
|
|
490
|
+
defaultOrigin: 'host',
|
|
491
|
+
messageTransport: createMessageTransport()
|
|
492
|
+
});
|
|
493
|
+
setupMessageTransportBridge(transport, iframe, this.iframeDomain);
|
|
494
|
+
const iframeDisplay = new IframeDisplayChannelAdapter(transport);
|
|
495
|
+
return {
|
|
496
|
+
iframe,
|
|
497
|
+
iframeDisplay,
|
|
498
|
+
cleanup: ()=>{
|
|
499
|
+
container.removeChild(iframe);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
} catch (error) {
|
|
503
|
+
this.logger.error('Error initializing iframe:', error);
|
|
504
|
+
throw error;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* this is called on class construction time
|
|
509
|
+
* @returns {Promise<void>} that resolves when the iframe is loaded and the message transport and iframe storage are initialized
|
|
510
|
+
*/ initializeIframeCommunication() {
|
|
511
|
+
if (!this.iframeLoadPromise) {
|
|
512
|
+
this.iframeLoadPromise = this.doInitializeIframeCommunication();
|
|
513
|
+
}
|
|
514
|
+
return this.iframeLoadPromise;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* initialize the iframe communication by awaiting the iframe load promise
|
|
518
|
+
* and initializing the message transport and iframe storage after iframe is successfully loaded
|
|
519
|
+
*/ async doInitializeIframeCommunication() {
|
|
520
|
+
try {
|
|
521
|
+
await this.loadIframe();
|
|
522
|
+
this.initializeMessageTransport();
|
|
523
|
+
this.initializeIframeStorage();
|
|
524
|
+
} catch (error) {
|
|
525
|
+
this.logger.error('Error initializing iframe:', error);
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* create a promise to load an iframe
|
|
531
|
+
* @returns {Promise<void>} that resolves when the iframe is loaded
|
|
532
|
+
*/ loadIframe() {
|
|
533
|
+
return new Promise((resolve, reject)=>{
|
|
534
|
+
const iframe = document.createElement('iframe');
|
|
535
|
+
const iframeTimeoutId = setTimeout(()=>{
|
|
536
|
+
reject(new Error('Iframe load timeout'));
|
|
537
|
+
}, 10000);
|
|
538
|
+
iframe.style.display = 'none';
|
|
539
|
+
iframe.setAttribute('title', 'Dynamic Wallet Iframe');
|
|
540
|
+
iframe.style.position = 'fixed';
|
|
541
|
+
iframe.style.top = '0';
|
|
542
|
+
iframe.style.left = '0';
|
|
543
|
+
iframe.style.width = '0';
|
|
544
|
+
iframe.style.height = '0';
|
|
545
|
+
iframe.style.border = 'none';
|
|
546
|
+
iframe.style.pointerEvents = 'none';
|
|
547
|
+
const params = new URLSearchParams({
|
|
548
|
+
instanceId: this.instanceId,
|
|
549
|
+
hostOrigin: window.location.origin
|
|
550
|
+
});
|
|
551
|
+
iframe.src = `${this.iframeDomain}/waas/${this.environmentId}?${params.toString()}`;
|
|
552
|
+
this.logger.debug('Creating iframe with src:', iframe.src);
|
|
553
|
+
document.body.appendChild(iframe);
|
|
554
|
+
iframe.onload = ()=>{
|
|
555
|
+
clearTimeout(iframeTimeoutId);
|
|
556
|
+
this.logger.debug('Iframe loaded successfully');
|
|
557
|
+
this.iframe = iframe;
|
|
558
|
+
resolve();
|
|
559
|
+
};
|
|
560
|
+
iframe.onerror = (error)=>{
|
|
561
|
+
clearTimeout(iframeTimeoutId);
|
|
562
|
+
this.logger.error('Iframe failed to load:', error);
|
|
563
|
+
reject(new Error('Failed to load iframe'));
|
|
564
|
+
};
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Load an iframe for a specific container
|
|
569
|
+
* @param {HTMLElement} container - The container to which the iframe will be attached
|
|
570
|
+
* @returns {Promise<HTMLIFrameElement>} that resolves when the iframe is loaded
|
|
571
|
+
*/ loadIframeForContainer(container) {
|
|
572
|
+
return new Promise((resolve, reject)=>{
|
|
573
|
+
const iframe = document.createElement('iframe');
|
|
574
|
+
const iframeTimeoutId = setTimeout(()=>{
|
|
575
|
+
reject(new Error('Iframe load timeout'));
|
|
576
|
+
}, 10000);
|
|
577
|
+
iframe.style.display = 'block';
|
|
578
|
+
iframe.style.width = '100%';
|
|
579
|
+
iframe.style.height = '100%';
|
|
580
|
+
iframe.setAttribute('title', 'Dynamic Wallet Storage');
|
|
581
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
|
|
582
|
+
const params = new URLSearchParams({
|
|
583
|
+
instanceId: this.instanceId,
|
|
584
|
+
hostOrigin: window.location.origin
|
|
585
|
+
});
|
|
586
|
+
iframe.src = `${this.iframeDomain}/waas/${this.environmentId}?${params.toString()}`;
|
|
587
|
+
this.logger.debug('Creating iframe with src:', iframe.src);
|
|
588
|
+
// Add iframe to the provided container
|
|
589
|
+
container.appendChild(iframe);
|
|
590
|
+
iframe.onload = ()=>{
|
|
591
|
+
clearTimeout(iframeTimeoutId);
|
|
592
|
+
this.logger.debug('Iframe loaded successfully');
|
|
593
|
+
this.iframe = iframe;
|
|
594
|
+
resolve(iframe);
|
|
595
|
+
};
|
|
596
|
+
iframe.onerror = (error)=>{
|
|
597
|
+
clearTimeout(iframeTimeoutId);
|
|
598
|
+
this.logger.error('Iframe failed to load:', error);
|
|
599
|
+
reject(new Error('Failed to load iframe'));
|
|
600
|
+
};
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* initialize the message transport after iframe is successfully loaded
|
|
605
|
+
*/ initializeMessageTransport() {
|
|
606
|
+
const transport = applyDefaultMessageOrigin({
|
|
607
|
+
defaultOrigin: 'host',
|
|
608
|
+
messageTransport: createMessageTransport()
|
|
609
|
+
});
|
|
610
|
+
this.messageTransport = transport;
|
|
611
|
+
if (!this.iframe) {
|
|
612
|
+
throw new Error('Iframe not available');
|
|
613
|
+
}
|
|
614
|
+
setupMessageTransportBridge(this.messageTransport, this.iframe, this.iframeDomain);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* initialize the iframe storage after iframe is successfully loaded
|
|
618
|
+
*/ initializeIframeStorage() {
|
|
619
|
+
if (!this.messageTransport) {
|
|
620
|
+
throw new Error('Message transport not initialized');
|
|
621
|
+
}
|
|
622
|
+
this.iframeStorage = new StorageRequestChannelAdapter(this.messageTransport);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Gets the initialized iframe instance. This method ensures the iframe is properly loaded and configured.
|
|
626
|
+
* The first call will initialize and await the iframe loading process.
|
|
627
|
+
* Subsequent calls will return the same iframe instance immediately.
|
|
628
|
+
*
|
|
629
|
+
* @throws {Error} If iframe initialization fails
|
|
630
|
+
* @throws {Error} If message transport initialization fails
|
|
631
|
+
* @throws {Error} If iframe storage initialization fails
|
|
632
|
+
* @returns {Promise<HTMLIFrameElement>} The initialized iframe element
|
|
633
|
+
*/ async getIframe() {
|
|
634
|
+
await this.initializeIframeCommunication();
|
|
635
|
+
if (!this.iframe) {
|
|
636
|
+
throw new Error('Failed to initialize iframe');
|
|
637
|
+
}
|
|
638
|
+
if (!this.messageTransport) {
|
|
639
|
+
throw new Error('Failed to initialize message transport');
|
|
640
|
+
}
|
|
641
|
+
if (!this.iframeStorage) {
|
|
642
|
+
throw new Error('Failed to initialize iframe storage');
|
|
643
|
+
}
|
|
644
|
+
return this.iframe;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Gets the initialized iframe storage instance. This method ensures the iframe storage is properly configured.
|
|
648
|
+
* The first call will initialize and await the iframe communication process.
|
|
649
|
+
* Subsequent calls will return the same storage instance immediately.
|
|
650
|
+
*
|
|
651
|
+
* @throws {Error} If iframe storage initialization fails
|
|
652
|
+
* @returns {Promise<SupportedStorage>} The initialized iframe storage instance
|
|
653
|
+
*/ async getIframeStorage() {
|
|
654
|
+
await this.initializeIframeCommunication();
|
|
655
|
+
if (!this.iframeStorage) {
|
|
656
|
+
throw new Error('Failed to initialize iframe storage');
|
|
657
|
+
}
|
|
658
|
+
return this.iframeStorage;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Client initialization logic
|
|
662
|
+
*/ async _initialize() {
|
|
663
|
+
try {
|
|
664
|
+
const initializePromises = [
|
|
665
|
+
this.restoreWallets(),
|
|
666
|
+
this.initializeIframeCommunication()
|
|
667
|
+
];
|
|
668
|
+
await Promise.all(initializePromises);
|
|
669
|
+
return {
|
|
670
|
+
error: null
|
|
671
|
+
};
|
|
672
|
+
} catch (error) {
|
|
673
|
+
return {
|
|
674
|
+
error
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
async serverInitializeKeyGen({ chainName, clientKeygenIds, thresholdSignatureScheme, onError, onCeremonyComplete }) {
|
|
679
|
+
// Initialize keygen, create room, and create the wallet account on the server
|
|
680
|
+
const data = await this.apiClient.createWalletAccount({
|
|
681
|
+
chainName,
|
|
682
|
+
clientKeygenIds,
|
|
683
|
+
thresholdSignatureScheme,
|
|
684
|
+
onError,
|
|
685
|
+
onCeremonyComplete
|
|
686
|
+
});
|
|
687
|
+
return data;
|
|
688
|
+
}
|
|
689
|
+
async clientInitializeKeyGen({ chainName, thresholdSignatureScheme }) {
|
|
690
|
+
// Get the mpc signer
|
|
691
|
+
const mpcSigner = getMPCSigner({
|
|
692
|
+
chainName,
|
|
693
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
694
|
+
});
|
|
695
|
+
const clientThreshold = getClientThreshold(thresholdSignatureScheme);
|
|
696
|
+
const keygenInitResults = await Promise.all(Array(clientThreshold).fill(null).map(()=>mpcSigner.initKeygen()));
|
|
697
|
+
return keygenInitResults;
|
|
698
|
+
}
|
|
699
|
+
async derivePublicKey({ chainName, keyShare, derivationPath }) {
|
|
700
|
+
const mpcSigner = getMPCSigner({
|
|
701
|
+
chainName,
|
|
702
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
703
|
+
});
|
|
704
|
+
let publicKey;
|
|
705
|
+
if (mpcSigner instanceof Ecdsa) {
|
|
706
|
+
publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
|
|
707
|
+
} else if (mpcSigner instanceof Ed25519) {
|
|
708
|
+
publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
|
|
709
|
+
}
|
|
710
|
+
return publicKey;
|
|
711
|
+
}
|
|
712
|
+
async clientKeyGen({ chainName, roomId, serverKeygenIds, clientKeygenInitResults, thresholdSignatureScheme }) {
|
|
713
|
+
// Get the chain config and the mpc signer
|
|
714
|
+
const mpcSigner = getMPCSigner({
|
|
715
|
+
chainName,
|
|
716
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
717
|
+
});
|
|
718
|
+
// Get the MPC config for the threshold signature scheme
|
|
719
|
+
const mpcConfig = MPC_CONFIG[thresholdSignatureScheme];
|
|
720
|
+
// For each client keygen init result, create an array of other parties' keygenIds
|
|
721
|
+
const clientKeygenResults = await Promise.all(clientKeygenInitResults.map((currentInit)=>{
|
|
722
|
+
// Get all other client keygenIds (excluding current one)
|
|
723
|
+
const otherClientKeygenIds = clientKeygenInitResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
|
|
724
|
+
// Combine server keygenIds with other client keygenIds
|
|
725
|
+
const allOtherKeygenIds = [
|
|
726
|
+
...serverKeygenIds,
|
|
727
|
+
...otherClientKeygenIds
|
|
728
|
+
];
|
|
729
|
+
return mpcSigner.keygen(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
|
|
730
|
+
}));
|
|
731
|
+
// only need one client keygen result to derive the public key
|
|
732
|
+
const [clientKeygenResult] = clientKeygenResults;
|
|
733
|
+
const chainConfig = getMPCChainConfig(chainName);
|
|
734
|
+
const derivationPath = new Uint32Array(chainConfig.derivationPath);
|
|
735
|
+
const rawPublicKey = await this.derivePublicKey({
|
|
736
|
+
chainName,
|
|
737
|
+
keyShare: clientKeygenResult,
|
|
738
|
+
derivationPath
|
|
739
|
+
});
|
|
740
|
+
return {
|
|
741
|
+
rawPublicKey,
|
|
742
|
+
clientKeygenResults
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
async keyGen({ chainName, thresholdSignatureScheme, onError, onCeremonyComplete }) {
|
|
746
|
+
try {
|
|
747
|
+
const clientKeygenInitResults = await this.clientInitializeKeyGen({
|
|
748
|
+
chainName,
|
|
749
|
+
thresholdSignatureScheme
|
|
750
|
+
});
|
|
751
|
+
const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
|
|
752
|
+
const { roomId, serverKeygenIds } = await this.serverInitializeKeyGen({
|
|
753
|
+
chainName,
|
|
754
|
+
clientKeygenIds,
|
|
755
|
+
thresholdSignatureScheme,
|
|
756
|
+
onCeremonyComplete
|
|
757
|
+
});
|
|
758
|
+
const { rawPublicKey, clientKeygenResults: clientKeyShares } = await this.clientKeyGen({
|
|
759
|
+
chainName,
|
|
760
|
+
roomId,
|
|
761
|
+
serverKeygenIds,
|
|
762
|
+
clientKeygenInitResults,
|
|
763
|
+
thresholdSignatureScheme
|
|
764
|
+
});
|
|
765
|
+
return {
|
|
766
|
+
rawPublicKey,
|
|
767
|
+
clientKeyShares
|
|
768
|
+
};
|
|
769
|
+
} catch (error) {
|
|
770
|
+
this.logger.error('Error creating wallet account', error);
|
|
771
|
+
throw new Error('Error creating wallet account');
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, onError, onCeremonyComplete }) {
|
|
775
|
+
const mpcSigner = getMPCSigner({
|
|
776
|
+
chainName,
|
|
777
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
778
|
+
});
|
|
779
|
+
const clientKeygenInitResults = await this.clientInitializeKeyGen({
|
|
780
|
+
chainName,
|
|
781
|
+
thresholdSignatureScheme
|
|
782
|
+
});
|
|
783
|
+
const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
|
|
784
|
+
const { roomId, serverKeygenIds } = await this.apiClient.importPrivateKey({
|
|
785
|
+
chainName,
|
|
786
|
+
clientKeygenIds,
|
|
787
|
+
thresholdSignatureScheme,
|
|
788
|
+
onError,
|
|
789
|
+
onCeremonyComplete
|
|
790
|
+
});
|
|
791
|
+
const { threshold } = getTSSConfig(thresholdSignatureScheme);
|
|
792
|
+
const clientKeygenResults = await Promise.all(clientKeygenInitResults.map(async (currentInit, index)=>{
|
|
793
|
+
const otherClientKeygenIds = clientKeygenInitResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
|
|
794
|
+
if (index === 0) {
|
|
795
|
+
const otherKeyGenIds = [
|
|
796
|
+
...serverKeygenIds,
|
|
797
|
+
...otherClientKeygenIds
|
|
798
|
+
];
|
|
799
|
+
const importerKeygenResult = await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
|
|
800
|
+
return importerKeygenResult;
|
|
801
|
+
} else {
|
|
802
|
+
const recipientKeygenResult = await mpcSigner.importPrivateKeyRecipient(roomId, threshold, currentInit, [
|
|
803
|
+
...serverKeygenIds,
|
|
804
|
+
...otherClientKeygenIds
|
|
805
|
+
]);
|
|
806
|
+
return recipientKeygenResult;
|
|
807
|
+
}
|
|
808
|
+
}));
|
|
809
|
+
const [clientKeygenResult] = clientKeygenResults;
|
|
810
|
+
const rawPublicKey = await this.derivePublicKey({
|
|
811
|
+
chainName,
|
|
812
|
+
keyShare: clientKeygenResult,
|
|
813
|
+
derivationPath: undefined
|
|
814
|
+
});
|
|
815
|
+
return {
|
|
816
|
+
rawPublicKey,
|
|
817
|
+
clientKeyShares: clientKeygenResults
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
async serverSign({ walletId, message }) {
|
|
821
|
+
// Create the room and sign the message
|
|
822
|
+
if (typeof message !== 'string') {
|
|
823
|
+
message = '0x' + Buffer.from(message).toString('hex');
|
|
824
|
+
}
|
|
825
|
+
const data = await this.apiClient.signMessage({
|
|
826
|
+
walletId,
|
|
827
|
+
message
|
|
828
|
+
});
|
|
829
|
+
return data;
|
|
830
|
+
}
|
|
831
|
+
async clientSign({ chainName, message, roomId, keyShare, derivationPath }) {
|
|
832
|
+
try {
|
|
833
|
+
const mpcSigner = getMPCSigner({
|
|
834
|
+
chainName,
|
|
835
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
836
|
+
});
|
|
837
|
+
let formattedMessage;
|
|
838
|
+
//note: Ecdsa can also be used by bitcoin, but only keccak256 is used by ethereum
|
|
839
|
+
if (mpcSigner instanceof Ecdsa) {
|
|
840
|
+
formattedMessage = MessageHash.keccak256(message);
|
|
841
|
+
} else if (mpcSigner instanceof Ed25519) {
|
|
842
|
+
if (typeof message === 'string') {
|
|
843
|
+
if (!isHexString(message)) {
|
|
844
|
+
formattedMessage = Buffer.from(message).toString('hex');
|
|
845
|
+
} else {
|
|
846
|
+
formattedMessage = Buffer.from(message, 'hex');
|
|
847
|
+
}
|
|
848
|
+
} else {
|
|
849
|
+
formattedMessage = message;
|
|
850
|
+
}
|
|
851
|
+
} else if (mpcSigner instanceof BIP340 && typeof message === 'string') {
|
|
852
|
+
formattedMessage = new TextEncoder().encode(message);
|
|
853
|
+
} else {
|
|
854
|
+
throw new Error('Unsupported signer type');
|
|
855
|
+
}
|
|
856
|
+
const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath);
|
|
857
|
+
return signature;
|
|
858
|
+
} catch (error) {
|
|
859
|
+
this.logger.error('Error in clientSign:', error);
|
|
860
|
+
throw error;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
//todo: need to modify with imported flag
|
|
864
|
+
async sign({ accountAddress, message, chainName, password = undefined }) {
|
|
865
|
+
await this.verifyPassword({
|
|
866
|
+
accountAddress,
|
|
867
|
+
password,
|
|
868
|
+
walletOperation: WalletOperation.SIGN_MESSAGE
|
|
869
|
+
});
|
|
870
|
+
const wallet = await this.getWallet({
|
|
871
|
+
accountAddress,
|
|
872
|
+
password,
|
|
873
|
+
walletOperation: WalletOperation.SIGN_MESSAGE
|
|
874
|
+
});
|
|
875
|
+
// Perform the server sign
|
|
876
|
+
const data = await this.serverSign({
|
|
877
|
+
walletId: wallet.walletId,
|
|
878
|
+
message
|
|
879
|
+
});
|
|
880
|
+
const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
|
|
881
|
+
// Perform the client sign and return the signature
|
|
882
|
+
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
883
|
+
accountAddress
|
|
884
|
+
});
|
|
885
|
+
const signature = await this.clientSign({
|
|
886
|
+
chainName,
|
|
887
|
+
message,
|
|
888
|
+
roomId: data.roomId,
|
|
889
|
+
keyShare: clientKeyShares[0],
|
|
890
|
+
derivationPath
|
|
891
|
+
});
|
|
892
|
+
return signature;
|
|
893
|
+
}
|
|
894
|
+
async refreshWalletAccountShares({ accountAddress, chainName, password = undefined }) {
|
|
895
|
+
await this.verifyPassword({
|
|
896
|
+
accountAddress,
|
|
897
|
+
password,
|
|
898
|
+
walletOperation: WalletOperation.REFRESH
|
|
899
|
+
});
|
|
900
|
+
const wallet = await this.getWallet({
|
|
901
|
+
accountAddress,
|
|
902
|
+
walletOperation: WalletOperation.NO_OPERATION,
|
|
903
|
+
password
|
|
904
|
+
});
|
|
905
|
+
const mpcSigner = getMPCSigner({
|
|
906
|
+
chainName,
|
|
907
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
908
|
+
});
|
|
909
|
+
// Create the room and refresh the shares
|
|
910
|
+
const data = await this.apiClient.refreshWalletAccountShares({
|
|
911
|
+
walletId: wallet.walletId
|
|
912
|
+
});
|
|
913
|
+
const roomId = data.roomId;
|
|
914
|
+
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
915
|
+
accountAddress
|
|
916
|
+
});
|
|
917
|
+
const refreshResults = await Promise.all(clientKeyShares.map((clientKeyShare)=>mpcSigner.refresh(roomId, clientKeyShare)));
|
|
918
|
+
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
919
|
+
clientKeySharesBackupInfo: getClientKeyShareBackupInfo()
|
|
920
|
+
});
|
|
921
|
+
await this.setClientKeySharesToLocalStorage({
|
|
922
|
+
accountAddress,
|
|
923
|
+
clientKeyShares: refreshResults,
|
|
924
|
+
overwriteOrMerge: 'overwrite'
|
|
925
|
+
});
|
|
926
|
+
await this.storeEncryptedBackupByWallet({
|
|
927
|
+
accountAddress,
|
|
928
|
+
password: password != null ? password : this.environmentId
|
|
929
|
+
});
|
|
930
|
+
return refreshResults;
|
|
931
|
+
}
|
|
932
|
+
async getExportId({ chainName, clientKeyShare }) {
|
|
933
|
+
const mpcSigner = getMPCSigner({
|
|
934
|
+
chainName,
|
|
935
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
936
|
+
});
|
|
937
|
+
const exportId = await mpcSigner.exportID(clientKeyShare);
|
|
938
|
+
return exportId;
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Helper function to create client shares required to complete a reshare ceremony.
|
|
942
|
+
* @param {string} chainName - The chain to create shares for
|
|
943
|
+
* @param {WalletProperties} wallet - The wallet to reshare
|
|
944
|
+
* @param {ThresholdSignatureScheme} oldThresholdSignatureScheme - The current threshold signature scheme
|
|
945
|
+
* @param {ThresholdSignatureScheme} newThresholdSignatureScheme - The target threshold signature scheme
|
|
946
|
+
* @returns {Promise<{
|
|
947
|
+
* newClientInitKeygenResults: ClientInitKeygenResult[],
|
|
948
|
+
* newClientKeygenIds: string[],
|
|
949
|
+
* existingClientKeygenIds: string[],
|
|
950
|
+
* existingClientKeyShares: ClientKeyShare[]
|
|
951
|
+
* }>} Object containing new and existing client keygen results, IDs and shares
|
|
952
|
+
* @todo Support higher to lower reshare strategies
|
|
953
|
+
*/ async reshareStrategy({ chainName, wallet, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme }) {
|
|
954
|
+
const mpcSigner = getMPCSigner({
|
|
955
|
+
chainName,
|
|
956
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
957
|
+
});
|
|
958
|
+
// Determine share counts based on threshold signature schemes
|
|
959
|
+
const { newClientShareCount, existingClientShareCount } = getReshareConfig({
|
|
960
|
+
oldThresholdSignatureScheme,
|
|
961
|
+
newThresholdSignatureScheme
|
|
962
|
+
});
|
|
963
|
+
// Create new client shares
|
|
964
|
+
const newClientInitKeygenResults = await Promise.all(Array.from({
|
|
965
|
+
length: newClientShareCount
|
|
966
|
+
}, ()=>mpcSigner.initKeygen()));
|
|
967
|
+
const newClientKeygenIds = newClientInitKeygenResults.map((result)=>result.keygenId);
|
|
968
|
+
// Get existing client shares
|
|
969
|
+
const existingClientKeyShares = (await this.getClientKeySharesFromLocalStorage({
|
|
970
|
+
accountAddress
|
|
971
|
+
})).slice(0, existingClientShareCount);
|
|
972
|
+
const existingClientKeygenIds = await Promise.all(existingClientKeyShares.map(async (keyShare)=>await this.getExportId({
|
|
973
|
+
chainName,
|
|
974
|
+
clientKeyShare: keyShare
|
|
975
|
+
})));
|
|
976
|
+
return {
|
|
977
|
+
newClientInitKeygenResults,
|
|
978
|
+
newClientKeygenIds,
|
|
979
|
+
existingClientKeygenIds,
|
|
980
|
+
existingClientKeyShares
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined }) {
|
|
984
|
+
await this.verifyPassword({
|
|
985
|
+
accountAddress,
|
|
986
|
+
password,
|
|
987
|
+
walletOperation: WalletOperation.RESHARE
|
|
988
|
+
});
|
|
989
|
+
const { existingClientShareCount } = getReshareConfig({
|
|
990
|
+
oldThresholdSignatureScheme,
|
|
991
|
+
newThresholdSignatureScheme
|
|
992
|
+
});
|
|
993
|
+
const wallet = await this.getWallet({
|
|
994
|
+
accountAddress,
|
|
995
|
+
walletOperation: WalletOperation.NO_OPERATION,
|
|
996
|
+
shareCount: existingClientShareCount,
|
|
997
|
+
password
|
|
998
|
+
});
|
|
999
|
+
const { newClientInitKeygenResults, newClientKeygenIds, existingClientKeygenIds, existingClientKeyShares } = await this.reshareStrategy({
|
|
1000
|
+
chainName,
|
|
1001
|
+
accountAddress,
|
|
1002
|
+
wallet,
|
|
1003
|
+
oldThresholdSignatureScheme,
|
|
1004
|
+
newThresholdSignatureScheme
|
|
1005
|
+
});
|
|
1006
|
+
const clientKeygenIds = [
|
|
1007
|
+
...newClientKeygenIds,
|
|
1008
|
+
...existingClientKeygenIds
|
|
1009
|
+
];
|
|
1010
|
+
// Server to create the room and complete the server reshare logics
|
|
1011
|
+
const data = await this.apiClient.reshare({
|
|
1012
|
+
walletId: wallet.walletId,
|
|
1013
|
+
clientKeygenIds: clientKeygenIds,
|
|
1014
|
+
oldThresholdSignatureScheme,
|
|
1015
|
+
newThresholdSignatureScheme
|
|
1016
|
+
});
|
|
1017
|
+
const { roomId, serverKeygenIds, newServerKeygenIds = [] } = data;
|
|
1018
|
+
// Get the MPC config for the threshold signature scheme
|
|
1019
|
+
const oldMpcConfig = MPC_CONFIG[oldThresholdSignatureScheme];
|
|
1020
|
+
const newMpcConfig = MPC_CONFIG[newThresholdSignatureScheme];
|
|
1021
|
+
const allPartyKeygenIds = [
|
|
1022
|
+
...clientKeygenIds,
|
|
1023
|
+
...serverKeygenIds,
|
|
1024
|
+
...newServerKeygenIds
|
|
1025
|
+
];
|
|
1026
|
+
const mpcSigner = getMPCSigner({
|
|
1027
|
+
chainName,
|
|
1028
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1029
|
+
});
|
|
1030
|
+
const reshareResults = await Promise.all([
|
|
1031
|
+
...newClientInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds)),
|
|
1032
|
+
...existingClientKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds))
|
|
1033
|
+
]);
|
|
1034
|
+
await this.setClientKeySharesToLocalStorage({
|
|
1035
|
+
accountAddress,
|
|
1036
|
+
clientKeyShares: reshareResults,
|
|
1037
|
+
overwriteOrMerge: 'overwrite'
|
|
1038
|
+
});
|
|
1039
|
+
await this.storeEncryptedBackupByWallet({
|
|
1040
|
+
accountAddress,
|
|
1041
|
+
password
|
|
1042
|
+
});
|
|
1043
|
+
return reshareResults;
|
|
1044
|
+
}
|
|
1045
|
+
async exportKey({ accountAddress, displayContainer, chainName, password = undefined }) {
|
|
1046
|
+
const wallet = await this.getWallet({
|
|
1047
|
+
accountAddress,
|
|
1048
|
+
password,
|
|
1049
|
+
walletOperation: WalletOperation.EXPORT_PRIVATE_KEY
|
|
1050
|
+
});
|
|
1051
|
+
const mpcSigner = getMPCSigner({
|
|
1052
|
+
chainName,
|
|
1053
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1054
|
+
});
|
|
1055
|
+
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1056
|
+
accountAddress
|
|
1057
|
+
});
|
|
1058
|
+
const exportId = await this.getExportId({
|
|
1059
|
+
chainName,
|
|
1060
|
+
clientKeyShare: clientKeyShares[0]
|
|
1061
|
+
});
|
|
1062
|
+
const data = await this.apiClient.exportKey({
|
|
1063
|
+
walletId: wallet.walletId,
|
|
1064
|
+
exportId
|
|
1065
|
+
});
|
|
1066
|
+
const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId, clientKeyShares[0], exportId);
|
|
1067
|
+
if (!keyExportRaw) {
|
|
1068
|
+
throw new Error('Error exporting private key');
|
|
1069
|
+
}
|
|
1070
|
+
const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
|
|
1071
|
+
let derivedPrivateKey;
|
|
1072
|
+
if (mpcSigner instanceof Ecdsa) {
|
|
1073
|
+
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
|
|
1074
|
+
} else if (mpcSigner instanceof Ed25519) {
|
|
1075
|
+
derivedPrivateKey = keyExportRaw;
|
|
1076
|
+
} else if (mpcSigner instanceof BIP340) {
|
|
1077
|
+
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
|
|
1078
|
+
}
|
|
1079
|
+
return {
|
|
1080
|
+
derivedPrivateKey
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
async offlineExportKey({ chainName, keyShares, derivationPath }) {
|
|
1084
|
+
try {
|
|
1085
|
+
if (!keyShares || keyShares.length < 2) {
|
|
1086
|
+
throw new Error(`Must provide at least min threshold of key shares`);
|
|
1087
|
+
}
|
|
1088
|
+
const mpcSigner = getMPCSigner({
|
|
1089
|
+
chainName,
|
|
1090
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1091
|
+
});
|
|
1092
|
+
const walletKeyShares = keyShares.map((keyShare)=>{
|
|
1093
|
+
return mpcSigner instanceof Ecdsa ? new EcdsaKeygenResult(keyShare.pubkey, keyShare.secretShare) : mpcSigner instanceof Ed25519 ? new Ed25519KeygenResult(keyShare.pubkey, keyShare.secretShare) : new BIP340KeygenResult(keyShare.pubkey, keyShare.secretShare);
|
|
1094
|
+
});
|
|
1095
|
+
const keyExportRaw = await mpcSigner.offlineExportFullPrivateKey(walletKeyShares);
|
|
1096
|
+
if (!keyExportRaw) {
|
|
1097
|
+
throw new Error('Error exporting private key: Export returned null');
|
|
1098
|
+
}
|
|
1099
|
+
const chainConfig = getMPCChainConfig(chainName);
|
|
1100
|
+
const walletDerivationPath = !derivationPath ? undefined : new Uint32Array(chainConfig.derivationPath);
|
|
1101
|
+
let derivedPrivateKey;
|
|
1102
|
+
if (mpcSigner instanceof Ecdsa) {
|
|
1103
|
+
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
|
|
1104
|
+
} else if (mpcSigner instanceof Ed25519) {
|
|
1105
|
+
derivedPrivateKey = keyExportRaw;
|
|
1106
|
+
} else if (mpcSigner instanceof BIP340) {
|
|
1107
|
+
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
|
|
1108
|
+
}
|
|
1109
|
+
const rawPublicKey = await this.derivePublicKey({
|
|
1110
|
+
chainName,
|
|
1111
|
+
keyShare: walletKeyShares[0],
|
|
1112
|
+
derivationPath: walletDerivationPath
|
|
1113
|
+
});
|
|
1114
|
+
return {
|
|
1115
|
+
derivedPrivateKey,
|
|
1116
|
+
rawPublicKey
|
|
1117
|
+
};
|
|
1118
|
+
} catch (error) {
|
|
1119
|
+
this.logger.error('Error in offlineExportKey:', error);
|
|
1120
|
+
throw error;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
async encryptKeyShare({ keyShare, password }) {
|
|
1124
|
+
const serializedKeyShare = JSON.stringify(keyShare);
|
|
1125
|
+
const encryptedKeyShare = await encryptData({
|
|
1126
|
+
data: serializedKeyShare,
|
|
1127
|
+
password: password != null ? password : this.environmentId
|
|
1128
|
+
});
|
|
1129
|
+
// stringify the encrypted key share, convert to base64, and store it
|
|
1130
|
+
const serializedEncryptedKeyShare = Buffer.from(JSON.stringify(encryptedKeyShare)).toString('base64');
|
|
1131
|
+
return serializedEncryptedKeyShare;
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* helper function to store encrypted backup by wallet from iframe local storage
|
|
1135
|
+
*/ async getClientKeySharesFromLocalStorage({ accountAddress }) {
|
|
1136
|
+
var _this_iframeStorage;
|
|
1137
|
+
await this.initializeIframeCommunication();
|
|
1138
|
+
const walletObject = await ((_this_iframeStorage = this.iframeStorage) == null ? void 0 : _this_iframeStorage.getItem(accountAddress));
|
|
1139
|
+
if (!walletObject) {
|
|
1140
|
+
this.logger.debug(`No item found in iframe local storage for accountAddress: ${accountAddress}`);
|
|
1141
|
+
return [];
|
|
1142
|
+
}
|
|
1143
|
+
try {
|
|
1144
|
+
return (walletObject == null ? void 0 : walletObject.clientKeyShares) || [];
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
this.logger.error(`Error parsing clientKeyShares: ${error} for accountAddress: ${accountAddress}`);
|
|
1147
|
+
return [];
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* helper function to store encrypted backup by wallet from iframe local storage
|
|
1152
|
+
*/ async setClientKeySharesToLocalStorage({ accountAddress, clientKeyShares, overwriteOrMerge = 'merge' }) {
|
|
1153
|
+
var _this_iframeStorage;
|
|
1154
|
+
await this.initializeIframeCommunication();
|
|
1155
|
+
await ((_this_iframeStorage = this.iframeStorage) == null ? void 0 : _this_iframeStorage.setItem(accountAddress, {
|
|
1156
|
+
clientKeyShares: overwriteOrMerge === 'overwrite' ? clientKeyShares : mergeUniqueKeyShares(await this.getClientKeySharesFromLocalStorage({
|
|
1157
|
+
accountAddress
|
|
1158
|
+
}), clientKeyShares)
|
|
1159
|
+
}));
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Encrypts and stores wallet key shares as backups.
|
|
1163
|
+
*
|
|
1164
|
+
* This method encrypts all client key shares for a specific wallet and stores them
|
|
1165
|
+
* in Dynamic's backend. If Google Drive backup is already configured for this wallet,
|
|
1166
|
+
* it will also update the backup in Google Drive.
|
|
1167
|
+
*
|
|
1168
|
+
* The method performs the following steps:
|
|
1169
|
+
* 1. Encrypts all client key shares with the provided password (or environment ID if no password)
|
|
1170
|
+
* 2. Stores the encrypted key shares in Dynamic's backend
|
|
1171
|
+
* 3. If Google Drive backup exists, updates the backup there as well
|
|
1172
|
+
* 4. Updates the local wallet map with the new backup information
|
|
1173
|
+
* 5. Persists the updated wallet map to storage
|
|
1174
|
+
*
|
|
1175
|
+
* @param {Object} params - The parameters for the backup operation
|
|
1176
|
+
* @param {string} params.accountAddress - The account address of the wallet to backup
|
|
1177
|
+
* @param {string} [params.password] - Optional password for encrypting the key shares
|
|
1178
|
+
*/ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined }) {
|
|
1179
|
+
const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromLocalStorage({
|
|
1180
|
+
accountAddress
|
|
1181
|
+
});
|
|
1182
|
+
const encryptedKeyShares = await Promise.all(keySharesToBackup.map((keyShare)=>this.encryptKeyShare({
|
|
1183
|
+
keyShare,
|
|
1184
|
+
password
|
|
1185
|
+
})));
|
|
1186
|
+
if (!this.walletMap[accountAddress].walletId) {
|
|
1187
|
+
throw new Error(`WalletId not found for accountAddress: ${accountAddress}`);
|
|
1188
|
+
}
|
|
1189
|
+
const data = await this.apiClient.storeEncryptedBackupByWallet({
|
|
1190
|
+
walletId: this.walletMap[accountAddress].walletId,
|
|
1191
|
+
encryptedKeyShares,
|
|
1192
|
+
passwordEncrypted: Boolean(password) && password !== this.environmentId
|
|
1193
|
+
});
|
|
1194
|
+
const hasGoogleDriveBackup = this.walletMap[accountAddress].clientKeySharesBackupInfo.backups[BackupLocation.GOOGLE_DRIVE].length > 0;
|
|
1195
|
+
if (hasGoogleDriveBackup) {
|
|
1196
|
+
var _user_verifiedCredentials_find;
|
|
1197
|
+
const user = await this.apiClient.getUser();
|
|
1198
|
+
const oauthAccountId = user == null ? void 0 : (_user_verifiedCredentials_find = user.verifiedCredentials.find((credential)=>credential.oauthProvider === 'google')) == null ? void 0 : _user_verifiedCredentials_find.id;
|
|
1199
|
+
const googleDriveKeyShareIds = await this.backupKeySharesToGoogleDrive({
|
|
1200
|
+
accountAddress,
|
|
1201
|
+
password,
|
|
1202
|
+
oauthAccountId
|
|
1203
|
+
});
|
|
1204
|
+
data.keyShares.push({
|
|
1205
|
+
backupLocation: BackupLocation.GOOGLE_DRIVE,
|
|
1206
|
+
id: googleDriveKeyShareIds
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
const updatedBackupInfo = getClientKeyShareBackupInfo({
|
|
1210
|
+
walletProperties: {
|
|
1211
|
+
derivationPath: this.walletMap[accountAddress].derivationPath,
|
|
1212
|
+
keyShares: data.keyShares,
|
|
1213
|
+
thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1217
|
+
clientKeySharesBackupInfo: updatedBackupInfo
|
|
1218
|
+
});
|
|
1219
|
+
await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
|
|
1220
|
+
return data;
|
|
1221
|
+
}
|
|
1222
|
+
async storeEncryptedBackupByWalletWithRetry({ accountAddress, clientKeyShares, password }) {
|
|
1223
|
+
await retryPromise(()=>this.storeEncryptedBackupByWallet({
|
|
1224
|
+
accountAddress,
|
|
1225
|
+
clientKeyShares,
|
|
1226
|
+
password
|
|
1227
|
+
}), {
|
|
1228
|
+
operationName: 'store encrypted backup',
|
|
1229
|
+
logContext: {
|
|
1230
|
+
walletAddress: accountAddress,
|
|
1231
|
+
keyShares: clientKeyShares == null ? void 0 : clientKeyShares.map((keyShare)=>this.encryptKeyShare({
|
|
1232
|
+
keyShare,
|
|
1233
|
+
password
|
|
1234
|
+
}))
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
async updatePassword({ accountAddress, existingPassword, newPassword }) {
|
|
1239
|
+
await this.getWallet({
|
|
1240
|
+
accountAddress,
|
|
1241
|
+
password: existingPassword,
|
|
1242
|
+
walletOperation: WalletOperation.REACH_ALL_PARTIES
|
|
1243
|
+
});
|
|
1244
|
+
await this.storeEncryptedBackupByWallet({
|
|
1245
|
+
accountAddress,
|
|
1246
|
+
password: newPassword
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
async decryptKeyShare({ keyShare, password }) {
|
|
1250
|
+
const decodedKeyShare = JSON.parse(Buffer.from(keyShare, 'base64').toString());
|
|
1251
|
+
const decryptedKeyShare = await decryptData({
|
|
1252
|
+
data: decodedKeyShare,
|
|
1253
|
+
password: password != null ? password : this.environmentId
|
|
1254
|
+
});
|
|
1255
|
+
const deserializedKeyShare = JSON.parse(decryptedKeyShare);
|
|
1256
|
+
return deserializedKeyShare;
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Helper function to determine keyshare recovery strategy for dynamic shares.
|
|
1260
|
+
* For REFRESH operations, retrieves enough shares to meet the client threshold.
|
|
1261
|
+
* For all other operations, retrieves just 1 share.
|
|
1262
|
+
*
|
|
1263
|
+
* @param clientKeyShareBackupInfo - Information about backed up key shares
|
|
1264
|
+
* @param thresholdSignatureScheme - The signature scheme being used (2-of-2, 2-of-3, etc)
|
|
1265
|
+
* @param walletOperation - The operation being performed (REFRESH, SIGN_MESSAGE, etc)
|
|
1266
|
+
* @param shareCount - The number of shares to recover if specified for reshare operations
|
|
1267
|
+
* @returns @shares: Object mapping backup locations to arrays of share IDs to recover
|
|
1268
|
+
* @returns @requiredShareCount: The number of shares required to recover
|
|
1269
|
+
*/ recoverStrategy({ clientKeyShareBackupInfo, thresholdSignatureScheme, walletOperation, shareCount = undefined }) {
|
|
1270
|
+
const { backups } = clientKeyShareBackupInfo;
|
|
1271
|
+
const { clientThreshold } = MPC_CONFIG[thresholdSignatureScheme];
|
|
1272
|
+
let requiredShareCount = walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.RESHARE ? clientThreshold : 1;
|
|
1273
|
+
// Override requiredShareCount if shareCount is provided
|
|
1274
|
+
if (shareCount !== undefined) {
|
|
1275
|
+
requiredShareCount = shareCount;
|
|
1276
|
+
}
|
|
1277
|
+
const dynamicShares = backups[BackupLocation.DYNAMIC].slice(0, requiredShareCount);
|
|
1278
|
+
return {
|
|
1279
|
+
shares: {
|
|
1280
|
+
[BackupLocation.DYNAMIC]: dynamicShares
|
|
1281
|
+
},
|
|
1282
|
+
requiredShareCount
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, shareCount = undefined, storeRecoveredShares = true }) {
|
|
1286
|
+
const wallet = this.walletMap[accountAddress];
|
|
1287
|
+
this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, wallet);
|
|
1288
|
+
const { shares } = this.recoverStrategy({
|
|
1289
|
+
clientKeyShareBackupInfo: wallet.clientKeySharesBackupInfo,
|
|
1290
|
+
thresholdSignatureScheme: wallet.thresholdSignatureScheme,
|
|
1291
|
+
walletOperation,
|
|
1292
|
+
shareCount
|
|
1293
|
+
});
|
|
1294
|
+
const { dynamic: dynamicKeyShareIds } = shares;
|
|
1295
|
+
const data = await this.apiClient.recoverEncryptedBackupByWallet({
|
|
1296
|
+
walletId: wallet.walletId,
|
|
1297
|
+
keyShareIds: dynamicKeyShareIds
|
|
1298
|
+
});
|
|
1299
|
+
const dynamicKeyShares = data.keyShares.filter((keyShare)=>keyShare.encryptedAccountCredential !== null && keyShare.backupLocation === BackupLocation.DYNAMIC);
|
|
1300
|
+
const decryptedKeyShares = await Promise.all(dynamicKeyShares.map((keyShare)=>this.decryptKeyShare({
|
|
1301
|
+
keyShare: keyShare.encryptedAccountCredential,
|
|
1302
|
+
password: password != null ? password : this.environmentId
|
|
1303
|
+
})));
|
|
1304
|
+
if (storeRecoveredShares) {
|
|
1305
|
+
await this.setClientKeySharesToLocalStorage({
|
|
1306
|
+
accountAddress,
|
|
1307
|
+
clientKeyShares: decryptedKeyShares,
|
|
1308
|
+
overwriteOrMerge: 'merge'
|
|
1309
|
+
});
|
|
1310
|
+
await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
|
|
1311
|
+
}
|
|
1312
|
+
return decryptedKeyShares;
|
|
1313
|
+
}
|
|
1314
|
+
async restoreWallets() {
|
|
1315
|
+
const wallets = await this.storage.getItem(this.storageKey);
|
|
1316
|
+
if (!wallets) {
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
this.walletMap = JSON.parse(wallets);
|
|
1320
|
+
}
|
|
1321
|
+
async backupKeySharesToGoogleDrive({ accountAddress, fileName, oauthAccountId, password }) {
|
|
1322
|
+
await this.getWallet({
|
|
1323
|
+
accountAddress,
|
|
1324
|
+
walletOperation: WalletOperation.REACH_ALL_PARTIES,
|
|
1325
|
+
password
|
|
1326
|
+
});
|
|
1327
|
+
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1328
|
+
accountAddress
|
|
1329
|
+
});
|
|
1330
|
+
if (clientKeyShares.length === 0) {
|
|
1331
|
+
throw new Error('No key shares found');
|
|
1332
|
+
}
|
|
1333
|
+
const encryptedKeyShares = await Promise.all(clientKeyShares.map((keyShare)=>this.encryptKeyShare({
|
|
1334
|
+
keyShare,
|
|
1335
|
+
password
|
|
1336
|
+
})));
|
|
1337
|
+
const accessToken = await this.apiClient.getAccessToken({
|
|
1338
|
+
oauthAccountId
|
|
1339
|
+
});
|
|
1340
|
+
const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
|
|
1341
|
+
const suggestedFileName = getClientKeyShareExportFileName({
|
|
1342
|
+
thresholdSignatureScheme,
|
|
1343
|
+
accountAddress
|
|
1344
|
+
});
|
|
1345
|
+
const backupData = {
|
|
1346
|
+
keyShares: encryptedKeyShares,
|
|
1347
|
+
metadata: {
|
|
1348
|
+
version: '1.0',
|
|
1349
|
+
createdAt: new Date().toISOString(),
|
|
1350
|
+
accountAddress,
|
|
1351
|
+
thresholdSignatureScheme,
|
|
1352
|
+
hasPassword: true,
|
|
1353
|
+
encryption: {
|
|
1354
|
+
algorithm: AES_GCM_ALGORITHM,
|
|
1355
|
+
keyDerivation: PBKDF2_ALGORITHM,
|
|
1356
|
+
iterations: PBKDF2_ITERATIONS,
|
|
1357
|
+
hashAlgorithm: PBKDF2_HASH_ALGORITHM,
|
|
1358
|
+
algorithmLength: AES_GCM_LENGTH
|
|
1359
|
+
},
|
|
1360
|
+
shareCount: encryptedKeyShares.length
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
// TODO: handle errors
|
|
1364
|
+
await Promise.all([
|
|
1365
|
+
uploadFileToGoogleDriveAppStorage({
|
|
1366
|
+
accessToken,
|
|
1367
|
+
fileName: fileName != null ? fileName : suggestedFileName,
|
|
1368
|
+
jsonData: backupData
|
|
1369
|
+
}),
|
|
1370
|
+
uploadFileToGoogleDrivePersonal({
|
|
1371
|
+
accessToken,
|
|
1372
|
+
fileName: fileName != null ? fileName : suggestedFileName,
|
|
1373
|
+
jsonData: backupData
|
|
1374
|
+
})
|
|
1375
|
+
]);
|
|
1376
|
+
const data = await this.apiClient.markKeySharesAsBackedUpGoogleDrive({
|
|
1377
|
+
walletId: this.walletMap[accountAddress].walletId
|
|
1378
|
+
});
|
|
1379
|
+
const ids = data.keyShares.map((keyShare)=>keyShare.id);
|
|
1380
|
+
this.walletMap[accountAddress].clientKeySharesBackupInfo.backups[BackupLocation.GOOGLE_DRIVE] = ids;
|
|
1381
|
+
await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
|
|
1382
|
+
return ids;
|
|
1383
|
+
}
|
|
1384
|
+
async restoreBackupFromGoogleDrive({ accountAddress, oauthAccountId, name, displayContainer, password }) {
|
|
1385
|
+
await this.getWallet({
|
|
1386
|
+
accountAddress
|
|
1387
|
+
});
|
|
1388
|
+
const accessToken = await this.apiClient.getAccessToken({
|
|
1389
|
+
oauthAccountId
|
|
1390
|
+
});
|
|
1391
|
+
const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
|
|
1392
|
+
const suggestedFileName = getClientKeyShareExportFileName({
|
|
1393
|
+
thresholdSignatureScheme,
|
|
1394
|
+
accountAddress
|
|
1395
|
+
});
|
|
1396
|
+
const backupData = await downloadFileFromGoogleDrive({
|
|
1397
|
+
accessToken,
|
|
1398
|
+
name: name != null ? name : suggestedFileName
|
|
1399
|
+
});
|
|
1400
|
+
if (!backupData) {
|
|
1401
|
+
throw new Error('No backup file found');
|
|
1402
|
+
}
|
|
1403
|
+
// Validate the backup data structure
|
|
1404
|
+
if (!backupData.keyShares || !backupData.metadata) {
|
|
1405
|
+
throw new Error('Invalid backup format: missing keyShares or metadata');
|
|
1406
|
+
}
|
|
1407
|
+
const { keyShares } = backupData;
|
|
1408
|
+
const decryptedKeyShares = await Promise.all(keyShares.map((keyShare)=>this.decryptKeyShare({
|
|
1409
|
+
keyShare,
|
|
1410
|
+
password
|
|
1411
|
+
})));
|
|
1412
|
+
await this.setClientKeySharesToLocalStorage({
|
|
1413
|
+
accountAddress,
|
|
1414
|
+
clientKeyShares: decryptedKeyShares,
|
|
1415
|
+
overwriteOrMerge: 'merge'
|
|
1416
|
+
});
|
|
1417
|
+
// Display the client shares in the container via iframe
|
|
1418
|
+
const { iframeDisplay } = await this.initializeIframeDisplayForContainer({
|
|
1419
|
+
container: displayContainer
|
|
1420
|
+
});
|
|
1421
|
+
iframeDisplay.displayClientShares(accountAddress);
|
|
1422
|
+
return decryptedKeyShares;
|
|
1423
|
+
}
|
|
1424
|
+
async exportClientKeyshares({ accountAddress, password }) {
|
|
1425
|
+
await this.verifyPassword({
|
|
1426
|
+
accountAddress,
|
|
1427
|
+
password,
|
|
1428
|
+
walletOperation: WalletOperation.REACH_ALL_PARTIES
|
|
1429
|
+
});
|
|
1430
|
+
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1431
|
+
accountAddress
|
|
1432
|
+
});
|
|
1433
|
+
if (!accountAddress) {
|
|
1434
|
+
throw new Error('Must provide an account address');
|
|
1435
|
+
}
|
|
1436
|
+
const derivationPath = this.walletMap[accountAddress].derivationPath;
|
|
1437
|
+
const text = JSON.stringify({
|
|
1438
|
+
keyShares: clientKeyShares,
|
|
1439
|
+
derivationPath
|
|
1440
|
+
});
|
|
1441
|
+
const blob = new Blob([
|
|
1442
|
+
text
|
|
1443
|
+
], {
|
|
1444
|
+
type: 'text/plain'
|
|
1445
|
+
});
|
|
1446
|
+
const url = URL.createObjectURL(blob);
|
|
1447
|
+
const a = document.createElement('a');
|
|
1448
|
+
a.href = url;
|
|
1449
|
+
a.download = `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${accountAddress}.txt`;
|
|
1450
|
+
a.click();
|
|
1451
|
+
}
|
|
1452
|
+
async getClientKeyShares({ accountAddress, password }) {
|
|
1453
|
+
await this.getWallet({
|
|
1454
|
+
accountAddress,
|
|
1455
|
+
password,
|
|
1456
|
+
walletOperation: WalletOperation.REACH_THRESHOLD
|
|
1457
|
+
});
|
|
1458
|
+
return this.getClientKeySharesFromLocalStorage({
|
|
1459
|
+
accountAddress
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Helper function to check if the required wallet fields are present and valid
|
|
1464
|
+
* @param accountAddress - The account address of the wallet to check
|
|
1465
|
+
* @param walletOperation - The wallet operation that determines required fields
|
|
1466
|
+
* @returns boolean indicating if wallet needs to be re-fetched and restored from server
|
|
1467
|
+
*/ async checkWalletFields({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD, shareCount }) {
|
|
1468
|
+
let keyshareCheck = false;
|
|
1469
|
+
let walletCheck = false;
|
|
1470
|
+
let thresholdSignatureSchemeCheck = false;
|
|
1471
|
+
let derivationPathCheck = false;
|
|
1472
|
+
// check if wallet exists
|
|
1473
|
+
const existingWallet = this.walletMap[accountAddress];
|
|
1474
|
+
if (existingWallet) {
|
|
1475
|
+
walletCheck = true;
|
|
1476
|
+
}
|
|
1477
|
+
// check if threshold signature scheme exists
|
|
1478
|
+
if (existingWallet == null ? void 0 : existingWallet.thresholdSignatureScheme) {
|
|
1479
|
+
thresholdSignatureSchemeCheck = true;
|
|
1480
|
+
}
|
|
1481
|
+
// check if derivation path exists
|
|
1482
|
+
if ((existingWallet == null ? void 0 : existingWallet.derivationPath) || (existingWallet == null ? void 0 : existingWallet.derivationPath) === '') {
|
|
1483
|
+
derivationPathCheck = true;
|
|
1484
|
+
}
|
|
1485
|
+
// check if wallet already exists with sufficient keyshares
|
|
1486
|
+
if (existingWallet) {
|
|
1487
|
+
const { shares } = this.recoverStrategy({
|
|
1488
|
+
clientKeyShareBackupInfo: existingWallet.clientKeySharesBackupInfo || {
|
|
1489
|
+
backups: getClientKeyShareBackupInfo()
|
|
1490
|
+
},
|
|
1491
|
+
thresholdSignatureScheme: existingWallet.thresholdSignatureScheme,
|
|
1492
|
+
walletOperation,
|
|
1493
|
+
shareCount
|
|
1494
|
+
});
|
|
1495
|
+
const { dynamic: requiredDynamicKeyShareIds = [] } = shares;
|
|
1496
|
+
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1497
|
+
accountAddress
|
|
1498
|
+
});
|
|
1499
|
+
if (requiredDynamicKeyShareIds.length <= ((clientKeyShares == null ? void 0 : clientKeyShares.length) || 0)) {
|
|
1500
|
+
keyshareCheck = true;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return walletCheck && thresholdSignatureSchemeCheck && keyshareCheck && derivationPathCheck;
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* verifyPassword attempts to recover and decrypt a single client key share using the provided password.
|
|
1507
|
+
* If successful, the key share is encrypted with the new password. This method solely performs the recovery
|
|
1508
|
+
* and decryption without storing the restored key shares. If unsuccessful, it throws an error.
|
|
1509
|
+
*/ async verifyPassword({ accountAddress, password = undefined, walletOperation = WalletOperation.NO_OPERATION }) {
|
|
1510
|
+
await this.getWallet({
|
|
1511
|
+
accountAddress,
|
|
1512
|
+
password,
|
|
1513
|
+
walletOperation
|
|
1514
|
+
});
|
|
1515
|
+
if (await this.requiresPasswordForOperation({
|
|
1516
|
+
accountAddress,
|
|
1517
|
+
walletOperation
|
|
1518
|
+
}) && !password) {
|
|
1519
|
+
throw new Error('Password is required for operation but not provided');
|
|
1520
|
+
}
|
|
1521
|
+
// silent return if no password is provided and operation does not require a password
|
|
1522
|
+
if (!password) {
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
const { backups } = await this.getWalletClientKeyShareBackupInfo({
|
|
1526
|
+
accountAddress
|
|
1527
|
+
});
|
|
1528
|
+
const { dynamic: dynamicKeyShareIds = [] } = backups;
|
|
1529
|
+
if (!dynamicKeyShareIds || dynamicKeyShareIds.length === 0) {
|
|
1530
|
+
throw new Error('No dynamic key shares found');
|
|
1531
|
+
}
|
|
1532
|
+
try {
|
|
1533
|
+
await this.recoverEncryptedBackupByWallet({
|
|
1534
|
+
accountAddress,
|
|
1535
|
+
password,
|
|
1536
|
+
walletOperation,
|
|
1537
|
+
shareCount: 1,
|
|
1538
|
+
storeRecoveredShares: false
|
|
1539
|
+
});
|
|
1540
|
+
} catch (error) {
|
|
1541
|
+
this.logger.error('Error in verifying password', error);
|
|
1542
|
+
throw new Error('Incorrect password');
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
async isPasswordEncrypted({ accountAddress }) {
|
|
1546
|
+
const clientKeySharesBackupInfo = await this.getWalletClientKeyShareBackupInfo({
|
|
1547
|
+
accountAddress
|
|
1548
|
+
});
|
|
1549
|
+
return clientKeySharesBackupInfo == null ? void 0 : clientKeySharesBackupInfo.passwordEncrypted;
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* check if the operation requires a password
|
|
1553
|
+
*/ async requiresPasswordForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD }) {
|
|
1554
|
+
const isEncrypted = await this.isPasswordEncrypted({
|
|
1555
|
+
accountAddress
|
|
1556
|
+
});
|
|
1557
|
+
if (!isEncrypted) {
|
|
1558
|
+
return false;
|
|
1559
|
+
}
|
|
1560
|
+
return this.requiresRestoreBackupSharesForOperation({
|
|
1561
|
+
accountAddress,
|
|
1562
|
+
walletOperation
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* check if the operation requires restoring backup shares
|
|
1567
|
+
*/ async requiresRestoreBackupSharesForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD }) {
|
|
1568
|
+
const clientKeySharesBackupInfo = await this.getWalletClientKeyShareBackupInfo({
|
|
1569
|
+
accountAddress
|
|
1570
|
+
});
|
|
1571
|
+
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1572
|
+
accountAddress
|
|
1573
|
+
});
|
|
1574
|
+
if (walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.RESHARE) {
|
|
1575
|
+
return true;
|
|
1576
|
+
}
|
|
1577
|
+
const { requiredShareCount } = this.recoverStrategy({
|
|
1578
|
+
clientKeyShareBackupInfo: clientKeySharesBackupInfo,
|
|
1579
|
+
thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme,
|
|
1580
|
+
walletOperation
|
|
1581
|
+
});
|
|
1582
|
+
if (clientKeyShares.length >= requiredShareCount) {
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
return true;
|
|
1586
|
+
}
|
|
1587
|
+
async getWalletClientKeyShareBackupInfo({ accountAddress }) {
|
|
1588
|
+
var _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups, _this_walletMap_accountAddress_clientKeySharesBackupInfo, _this_walletMap_accountAddress, _user_verifiedCredentials;
|
|
1589
|
+
// Return existing backup info if it exists
|
|
1590
|
+
if (((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo = _this_walletMap_accountAddress.clientKeySharesBackupInfo) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups = _this_walletMap_accountAddress_clientKeySharesBackupInfo.backups) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC = _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups[BackupLocation.DYNAMIC]) == null ? void 0 : _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC.length) > 0) {
|
|
1591
|
+
return this.walletMap[accountAddress].clientKeySharesBackupInfo;
|
|
1592
|
+
}
|
|
1593
|
+
// Get backup info from server
|
|
1594
|
+
const user = await this.apiClient.getUser();
|
|
1595
|
+
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
1596
|
+
return getClientKeyShareBackupInfo({
|
|
1597
|
+
walletProperties: wallet == null ? void 0 : wallet.walletProperties
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
async getWallet({ accountAddress, walletOperation = WalletOperation.NO_OPERATION, shareCount = undefined, password = undefined }) {
|
|
1601
|
+
var _user_verifiedCredentials;
|
|
1602
|
+
const existingWalletCheck = await this.checkWalletFields({
|
|
1603
|
+
accountAddress,
|
|
1604
|
+
walletOperation,
|
|
1605
|
+
shareCount
|
|
1606
|
+
});
|
|
1607
|
+
if (existingWalletCheck) {
|
|
1608
|
+
this.logger.debug(`Wallet ${accountAddress} already exists`);
|
|
1609
|
+
return this.walletMap[accountAddress];
|
|
1610
|
+
}
|
|
1611
|
+
// Fetch and restore wallet from server
|
|
1612
|
+
const user = await this.apiClient.getUser();
|
|
1613
|
+
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
1614
|
+
this.logger.debug('Restoring wallet', wallet);
|
|
1615
|
+
const walletProperties = wallet.walletProperties;
|
|
1616
|
+
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1617
|
+
walletId: wallet.id,
|
|
1618
|
+
chainName: wallet.chainName,
|
|
1619
|
+
accountAddress,
|
|
1620
|
+
thresholdSignatureScheme: walletProperties.thresholdSignatureScheme,
|
|
1621
|
+
derivationPath: walletProperties.derivationPath,
|
|
1622
|
+
clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
|
|
1623
|
+
walletProperties
|
|
1624
|
+
})
|
|
1625
|
+
});
|
|
1626
|
+
if (walletOperation !== WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
|
|
1627
|
+
accountAddress,
|
|
1628
|
+
walletOperation
|
|
1629
|
+
})) {
|
|
1630
|
+
const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
|
|
1631
|
+
accountAddress,
|
|
1632
|
+
password: password != null ? password : this.environmentId,
|
|
1633
|
+
walletOperation: walletOperation,
|
|
1634
|
+
shareCount
|
|
1635
|
+
});
|
|
1636
|
+
const existingKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1637
|
+
accountAddress
|
|
1638
|
+
});
|
|
1639
|
+
await this.setClientKeySharesToLocalStorage({
|
|
1640
|
+
accountAddress,
|
|
1641
|
+
clientKeyShares: mergeUniqueKeyShares(existingKeyShares, decryptedKeyShares)
|
|
1642
|
+
});
|
|
1643
|
+
this.logger.debug('Recovered backup', decryptedKeyShares);
|
|
1644
|
+
}
|
|
1645
|
+
const walletCount = Object.keys(this.walletMap).length;
|
|
1646
|
+
if (walletCount === 0) {
|
|
1647
|
+
throw new Error('No wallets found');
|
|
1648
|
+
}
|
|
1649
|
+
// Return the only wallet if there's just one
|
|
1650
|
+
if (walletCount === 1) {
|
|
1651
|
+
return Object.values(this.walletMap)[0];
|
|
1652
|
+
}
|
|
1653
|
+
return this.walletMap[accountAddress];
|
|
1654
|
+
}
|
|
1655
|
+
async getWallets() {
|
|
1656
|
+
var _user_verifiedCredentials;
|
|
1657
|
+
const user = await this.apiClient.getUser();
|
|
1658
|
+
const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
|
|
1659
|
+
const wallets = waasWallets.map((vc)=>{
|
|
1660
|
+
var _this_walletMap_vc_address, _vc_walletProperties;
|
|
1661
|
+
var _this_walletMap_vc_address_derivationPath;
|
|
1662
|
+
return {
|
|
1663
|
+
walletId: vc.id,
|
|
1664
|
+
chainName: vc.chain,
|
|
1665
|
+
accountAddress: vc.address,
|
|
1666
|
+
clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
|
|
1667
|
+
walletProperties: vc.walletProperties || {}
|
|
1668
|
+
}),
|
|
1669
|
+
derivationPath: (_this_walletMap_vc_address_derivationPath = (_this_walletMap_vc_address = this.walletMap[vc.address]) == null ? void 0 : _this_walletMap_vc_address.derivationPath) != null ? _this_walletMap_vc_address_derivationPath : undefined,
|
|
1670
|
+
thresholdSignatureScheme: (_vc_walletProperties = vc.walletProperties) == null ? void 0 : _vc_walletProperties.thresholdSignatureScheme
|
|
1671
|
+
};
|
|
1672
|
+
});
|
|
1673
|
+
this.walletMap = wallets.reduce((acc, wallet)=>{
|
|
1674
|
+
var _acc_accountAddress;
|
|
1675
|
+
const accountAddress = wallet.accountAddress;
|
|
1676
|
+
acc[wallet.accountAddress] = {
|
|
1677
|
+
walletId: wallet.walletId,
|
|
1678
|
+
chainName: wallet.chainName,
|
|
1679
|
+
accountAddress: wallet.accountAddress,
|
|
1680
|
+
clientKeySharesBackupInfo: wallet.clientKeySharesBackupInfo,
|
|
1681
|
+
derivationPath: ((_acc_accountAddress = acc[accountAddress]) == null ? void 0 : _acc_accountAddress.derivationPath) || undefined,
|
|
1682
|
+
thresholdSignatureScheme: wallet.thresholdSignatureScheme
|
|
1683
|
+
};
|
|
1684
|
+
return acc;
|
|
1685
|
+
}, {});
|
|
1686
|
+
return wallets;
|
|
1687
|
+
}
|
|
1688
|
+
constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug }){
|
|
1689
|
+
this.initializePromise = null;
|
|
1690
|
+
this.logger = logger;
|
|
1691
|
+
this.walletMap = {} // todo: store in session storage
|
|
1692
|
+
;
|
|
1693
|
+
this.iframeStorage = null;
|
|
1694
|
+
this.iframeDisplay = null;
|
|
1695
|
+
this.memoryStorage = null;
|
|
1696
|
+
this.messageTransport = null;
|
|
1697
|
+
this.iframe = null;
|
|
1698
|
+
this.iframeLoadPromise = null;
|
|
1699
|
+
this.environmentId = environmentId;
|
|
1700
|
+
this.storageKey = `${STORAGE_KEY}-${storageKey != null ? storageKey : environmentId}`;
|
|
1701
|
+
this.baseMPCRelayApiUrl = baseMPCRelayApiUrl;
|
|
1702
|
+
this.apiClient = new DynamicApiClient({
|
|
1703
|
+
environmentId,
|
|
1704
|
+
authToken,
|
|
1705
|
+
baseApiUrl
|
|
1706
|
+
});
|
|
1707
|
+
this.debug = Boolean(debug);
|
|
1708
|
+
this.logger.setLogLevel(this.debug ? 'DEBUG' : DEFAULT_LOG_LEVEL);
|
|
1709
|
+
// setup storage
|
|
1710
|
+
if (supportsLocalStorage()) {
|
|
1711
|
+
this.storage = localStorageAdapter;
|
|
1712
|
+
} else {
|
|
1713
|
+
this.memoryStorage = {};
|
|
1714
|
+
this.storage = memoryLocalStorageAdapter(this.memoryStorage);
|
|
1715
|
+
}
|
|
1716
|
+
// TODO: handle prod and preprod iframe domains
|
|
1717
|
+
this.iframeDomain = IFRAME_DOMAIN_PREPROD;
|
|
1718
|
+
// Generate unique instanceId when client is created
|
|
1719
|
+
this.instanceId = crypto.randomUUID();
|
|
1720
|
+
// initialize the client
|
|
1721
|
+
this.initialize();
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
export { DynamicWalletClient, base64ToBytes, bytesToBase64, ensureBase64Padding, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getMPCSignatureScheme, getMPCSigner, isBrowser, isHexString, mergeUniqueKeyShares, retryPromise, stringToBytes, timeoutPromise };
|