@dynamic-labs-wallet/browser 0.0.0-beta.310 → 0.0.0-beta.312
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.js +621 -498
- package/index.esm.js +625 -498
- package/internal/core/package.json +17 -0
- package/internal/web/package.json +17 -0
- package/package.json +10 -3
- package/src/backup/encryption/argon2.d.ts +10 -0
- package/src/backup/encryption/argon2.d.ts.map +1 -0
- package/src/backup/encryption/config.d.ts +39 -0
- package/src/backup/encryption/config.d.ts.map +1 -0
- package/src/backup/encryption/constants.d.ts +35 -0
- package/src/backup/encryption/constants.d.ts.map +1 -0
- package/src/backup/encryption/core.d.ts +27 -0
- package/src/backup/encryption/core.d.ts.map +1 -0
- package/src/backup/encryption/pbkdf2.d.ts +10 -0
- package/src/backup/encryption/pbkdf2.d.ts.map +1 -0
- package/src/backup/encryption/types.d.ts +46 -0
- package/src/backup/encryption/types.d.ts.map +1 -0
- package/src/backup/encryption/utils.d.ts +9 -0
- package/src/backup/encryption/utils.d.ts.map +1 -0
- package/src/client.d.ts +11 -23
- package/src/client.d.ts.map +1 -1
- package/src/index.d.ts +6 -6
- package/src/index.d.ts.map +1 -1
- package/src/mpc/index.d.ts +3 -3
- package/src/mpc/index.d.ts.map +1 -1
- package/src/mpc/mpc.d.ts +1 -1
- package/src/mpc/types.d.ts +2 -2
- package/src/services/axiosErrorResponse.d.ts.map +1 -1
- package/src/services/encryption.d.ts +19 -0
- package/src/services/encryption.d.ts.map +1 -0
- package/src/types.d.ts +1 -1
- package/src/types.d.ts.map +1 -1
- package/src/utils.d.ts +3 -7
- package/src/utils.d.ts.map +1 -1
- package/src/backup/encryption.d.ts +0 -62
- package/src/backup/encryption.d.ts.map +0 -1
package/index.cjs.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var core = require('@dynamic-labs-wallet/core');
|
|
4
|
-
var web = require('
|
|
5
|
-
var uuid = require('uuid');
|
|
4
|
+
var web = require('#internal/web');
|
|
6
5
|
var semver = require('semver');
|
|
6
|
+
var uuid = require('uuid');
|
|
7
7
|
var logger$1 = require('@dynamic-labs/logger');
|
|
8
|
-
var
|
|
8
|
+
var loadArgon2idWasm = require('argon2id');
|
|
9
9
|
var axios = require('axios');
|
|
10
10
|
var createHttpError = require('http-errors');
|
|
11
|
+
var sdkApiCore = require('@dynamic-labs/sdk-api-core');
|
|
11
12
|
|
|
12
13
|
function _extends() {
|
|
13
14
|
_extends = Object.assign || function assign(target) {
|
|
@@ -41,65 +42,118 @@ const getMPCSigner = ({ chainName, baseRelayUrl })=>{
|
|
|
41
42
|
return signatureScheme;
|
|
42
43
|
};
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Encryption version identifiers
|
|
47
|
+
*/ var EncryptionVersion = /*#__PURE__*/ function(EncryptionVersion) {
|
|
48
|
+
EncryptionVersion["V1_LEGACY"] = "v1";
|
|
49
|
+
EncryptionVersion["V2_PBKDF2"] = "v2";
|
|
50
|
+
EncryptionVersion["V3_ARGON2"] = "v3";
|
|
51
|
+
return EncryptionVersion;
|
|
52
|
+
}({});
|
|
53
|
+
/**
|
|
54
|
+
* Current default version for new encryptions
|
|
55
|
+
*/ const ENCRYPTION_VERSION_CURRENT = "v3";
|
|
56
|
+
/**
|
|
57
|
+
* Algorithm constants
|
|
58
|
+
*/ const PBKDF2_ALGORITHM = 'PBKDF2';
|
|
59
|
+
const HASH_ALGORITHM = 'SHA-256'; // Generic hash algorithm constant
|
|
60
|
+
const ARGON2_ALGORITHM = 'Argon2id';
|
|
61
|
+
const AES_GCM_ALGORITHM = 'AES-GCM';
|
|
62
|
+
const AES_GCM_LENGTH = 256;
|
|
63
|
+
/**
|
|
64
|
+
* Argon2 configuration constants, values were chosen based on RFC (https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice)
|
|
65
|
+
* taking into account that this runs in the client, possibly in smartphones with limited resources
|
|
66
|
+
*/ const ARGON2_MEMORY_SIZE = 65536; // 64 MB in KiB
|
|
67
|
+
const ARGON2_ITERATIONS = 3;
|
|
68
|
+
const ARGON2_PARALLELISM = 2;
|
|
69
|
+
const ARGON2_HASH_LENGTH = 32;
|
|
70
|
+
/**
|
|
71
|
+
* PBKDF2 configuration constants
|
|
72
|
+
*/ const PBKDF2_ITERATIONS_V1 = 100000;
|
|
73
|
+
const PBKDF2_ITERATIONS_V2 = 1000000;
|
|
53
74
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Derives a key using Argon2id algorithm
|
|
77
|
+
* @param params - Key derivation parameters
|
|
78
|
+
* @param encryptionConfig - Encryption configuration
|
|
79
|
+
* @returns Promise<CryptoKey>
|
|
80
|
+
*/ const deriveArgon2Key = async ({ password, salt }, encryptionConfig)=>{
|
|
81
|
+
const argon2id = await loadArgon2idWasm();
|
|
82
|
+
const argon2Config = encryptionConfig;
|
|
83
|
+
const passwordBytes = new TextEncoder().encode(password);
|
|
84
|
+
const hash = argon2id({
|
|
85
|
+
password: passwordBytes,
|
|
86
|
+
salt: salt,
|
|
87
|
+
parallelism: argon2Config.parallelism || ARGON2_PARALLELISM,
|
|
88
|
+
passes: argon2Config.iterations,
|
|
89
|
+
memorySize: argon2Config.memorySize || ARGON2_MEMORY_SIZE,
|
|
90
|
+
tagLength: argon2Config.hashLength || ARGON2_HASH_LENGTH
|
|
60
91
|
});
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
case 422:
|
|
69
|
-
throw createHttpError(422, 'Unprocessable content');
|
|
70
|
-
case 500:
|
|
71
|
-
throw createHttpError(500, 'Internal server error');
|
|
72
|
-
default:
|
|
73
|
-
throw createHttpError(500, 'Internal server error');
|
|
74
|
-
}
|
|
92
|
+
return crypto.subtle.importKey('raw', new Uint8Array(hash), {
|
|
93
|
+
name: encryptionConfig.algorithm,
|
|
94
|
+
length: encryptionConfig.algorithmLength
|
|
95
|
+
}, false, [
|
|
96
|
+
'encrypt',
|
|
97
|
+
'decrypt'
|
|
98
|
+
]);
|
|
75
99
|
};
|
|
76
100
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Encryption configuration for each version
|
|
103
|
+
*/ const ENCRYPTION_VERSIONS = {
|
|
104
|
+
[EncryptionVersion.V1_LEGACY]: {
|
|
105
|
+
version: EncryptionVersion.V1_LEGACY,
|
|
106
|
+
algorithm: AES_GCM_ALGORITHM,
|
|
107
|
+
keyDerivation: PBKDF2_ALGORITHM,
|
|
108
|
+
iterations: PBKDF2_ITERATIONS_V1,
|
|
109
|
+
hashAlgorithm: HASH_ALGORITHM,
|
|
110
|
+
algorithmLength: AES_GCM_LENGTH
|
|
111
|
+
},
|
|
112
|
+
[EncryptionVersion.V2_PBKDF2]: {
|
|
113
|
+
version: EncryptionVersion.V2_PBKDF2,
|
|
114
|
+
algorithm: AES_GCM_ALGORITHM,
|
|
115
|
+
keyDerivation: PBKDF2_ALGORITHM,
|
|
116
|
+
iterations: PBKDF2_ITERATIONS_V2,
|
|
117
|
+
hashAlgorithm: HASH_ALGORITHM,
|
|
118
|
+
algorithmLength: AES_GCM_LENGTH
|
|
119
|
+
},
|
|
120
|
+
[EncryptionVersion.V3_ARGON2]: {
|
|
121
|
+
version: EncryptionVersion.V3_ARGON2,
|
|
122
|
+
algorithm: AES_GCM_ALGORITHM,
|
|
123
|
+
keyDerivation: ARGON2_ALGORITHM,
|
|
124
|
+
iterations: ARGON2_ITERATIONS,
|
|
125
|
+
hashAlgorithm: ARGON2_ALGORITHM,
|
|
126
|
+
algorithmLength: AES_GCM_LENGTH,
|
|
127
|
+
memorySize: ARGON2_MEMORY_SIZE,
|
|
128
|
+
parallelism: ARGON2_PARALLELISM,
|
|
129
|
+
hashLength: ARGON2_HASH_LENGTH
|
|
90
130
|
}
|
|
91
131
|
};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Helper function to get encryption configuration by version string
|
|
134
|
+
* @param version - The version string (e.g., 'v1', 'v2', 'v3')
|
|
135
|
+
* @returns The encryption configuration for the specified version
|
|
136
|
+
*/ const getEncryptionConfig = (version)=>{
|
|
137
|
+
// If no version specified, use legacy for backward compatibility
|
|
138
|
+
if (!version) {
|
|
139
|
+
return ENCRYPTION_VERSIONS[EncryptionVersion.V1_LEGACY];
|
|
140
|
+
}
|
|
141
|
+
const config = ENCRYPTION_VERSIONS[version];
|
|
142
|
+
if (!config) {
|
|
143
|
+
throw new Error(`Unsupported encryption version: ${version}`);
|
|
95
144
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
145
|
+
return config;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Check if a configuration uses Argon2
|
|
149
|
+
*/ const isArgon2Config = (config)=>{
|
|
150
|
+
return config.keyDerivation === ARGON2_ALGORITHM;
|
|
100
151
|
};
|
|
101
152
|
|
|
102
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Utility functions for encryption operations
|
|
155
|
+
* These functions are separated to avoid circular dependencies
|
|
156
|
+
*/ const bytesToBase64 = (arr)=>{
|
|
103
157
|
return btoa(Array.from(arr, (b)=>String.fromCharCode(b)).join(''));
|
|
104
158
|
};
|
|
105
159
|
const stringToBytes = (str)=>{
|
|
@@ -112,168 +166,13 @@ const base64ToBytes = (base64)=>{
|
|
|
112
166
|
const ensureBase64Padding = (str)=>{
|
|
113
167
|
return str.padEnd(Math.ceil(str.length / 4) * 4, '=');
|
|
114
168
|
};
|
|
115
|
-
const isBrowser = ()=>typeof window !== 'undefined';
|
|
116
|
-
const getClientKeyShareExportFileName = ({ thresholdSignatureScheme, accountAddress })=>{
|
|
117
|
-
return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}.json`;
|
|
118
|
-
};
|
|
119
|
-
const getClientKeyShareBackupInfo = (params)=>{
|
|
120
|
-
var _params_walletProperties, _params_walletProperties_keyShares_;
|
|
121
|
-
const backups = {
|
|
122
|
-
[core.BackupLocation.DYNAMIC]: [],
|
|
123
|
-
[core.BackupLocation.GOOGLE_DRIVE]: [],
|
|
124
|
-
[core.BackupLocation.ICLOUD]: [],
|
|
125
|
-
[core.BackupLocation.USER]: [],
|
|
126
|
-
[core.BackupLocation.EXTERNAL]: []
|
|
127
|
-
};
|
|
128
|
-
if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
|
|
129
|
-
return {
|
|
130
|
-
backups,
|
|
131
|
-
passwordEncrypted: false
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
params.walletProperties.keyShares.forEach((keyShare)=>{
|
|
135
|
-
if (backups[keyShare.backupLocation]) {
|
|
136
|
-
backups[keyShare.backupLocation].push({
|
|
137
|
-
location: keyShare.backupLocation,
|
|
138
|
-
keyShareId: keyShare.id,
|
|
139
|
-
externalKeyShareId: keyShare == null ? void 0 : keyShare.externalKeyShareId
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
const passwordEncrypted = Boolean((_params_walletProperties_keyShares_ = params.walletProperties.keyShares[0]) == null ? void 0 : _params_walletProperties_keyShares_.passwordEncrypted);
|
|
144
|
-
return {
|
|
145
|
-
backups,
|
|
146
|
-
passwordEncrypted
|
|
147
|
-
};
|
|
148
|
-
};
|
|
149
|
-
/**
|
|
150
|
-
* Helper function to merge keyshares and remove duplicates based on pubkey and secretShare
|
|
151
|
-
* @param existingKeyShares - Array of existing keyshares
|
|
152
|
-
* @param newKeyShares - Array of new keyshares to merge
|
|
153
|
-
* @returns Array of merged unique keyshares
|
|
154
|
-
*/ const mergeUniqueKeyShares = (existingKeyShares, newKeyShares)=>{
|
|
155
|
-
const uniqueKeyShares = newKeyShares.filter((newShare)=>!existingKeyShares.some((existingShare)=>{
|
|
156
|
-
if (!(newShare == null ? void 0 : newShare.pubkey) || !(existingShare == null ? void 0 : existingShare.pubkey)) return false;
|
|
157
|
-
return newShare.pubkey.toString() === existingShare.pubkey.toString() && newShare.secretShare === existingShare.secretShare;
|
|
158
|
-
}));
|
|
159
|
-
return [
|
|
160
|
-
...existingKeyShares,
|
|
161
|
-
...uniqueKeyShares
|
|
162
|
-
];
|
|
163
|
-
};
|
|
164
|
-
const timeoutPromise = ({ timeInMs, activity = 'Ceremony' })=>{
|
|
165
|
-
return new Promise((_, reject)=>setTimeout(()=>reject(new Error(`${activity} did not complete in ${timeInMs}ms`)), timeInMs));
|
|
166
|
-
};
|
|
167
|
-
/**
|
|
168
|
-
* Generic helper function to retry a promise-based operations
|
|
169
|
-
*
|
|
170
|
-
* @param operation - The async operation to retry
|
|
171
|
-
* @param config - Configuration options for retry behavior
|
|
172
|
-
* @returns Promise with the operation result
|
|
173
|
-
* @throws Last error encountered after all retries are exhausted
|
|
174
|
-
*/ async function retryPromise(operation, { maxAttempts = 5, retryInterval = 500, operationName = 'operation', logContext = {} } = {}) {
|
|
175
|
-
let attempts = 0;
|
|
176
|
-
while(attempts < maxAttempts){
|
|
177
|
-
try {
|
|
178
|
-
return await operation();
|
|
179
|
-
} catch (error) {
|
|
180
|
-
attempts++;
|
|
181
|
-
if (attempts === maxAttempts) {
|
|
182
|
-
logger.error(`Failed to execute ${operationName} after ${maxAttempts} attempts`, _extends({}, logContext, {
|
|
183
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
184
|
-
}));
|
|
185
|
-
throw error;
|
|
186
|
-
}
|
|
187
|
-
// Calculate exponential backoff delay
|
|
188
|
-
const exponentialDelay = retryInterval * 2 ** (attempts - 1);
|
|
189
|
-
await new Promise((resolve)=>setTimeout(resolve, exponentialDelay));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
// TypeScript needs this even though it's unreachable
|
|
193
|
-
throw new Error('Unreachable code');
|
|
194
|
-
}
|
|
195
|
-
const formatEvmMessage = (message)=>{
|
|
196
|
-
if (typeof message === 'string' && message.startsWith('0x')) {
|
|
197
|
-
const serializedTxBytes = Uint8Array.from(Buffer.from(message.slice(2), 'hex'));
|
|
198
|
-
return web.MessageHash.keccak256(serializedTxBytes);
|
|
199
|
-
}
|
|
200
|
-
return web.MessageHash.keccak256(message);
|
|
201
|
-
};
|
|
202
|
-
const isHexString = (str)=>{
|
|
203
|
-
// Remove 0x prefix if present
|
|
204
|
-
const hex = str.startsWith('0x') ? str.slice(2) : str;
|
|
205
|
-
// Check if string contains only hex characters
|
|
206
|
-
return /^[0-9A-Fa-f]+$/.test(hex);
|
|
207
|
-
};
|
|
208
|
-
const formatSolanaMessage = (message)=>{
|
|
209
|
-
if (typeof message === 'string') {
|
|
210
|
-
if (!isHexString(message)) {
|
|
211
|
-
return Buffer.from(message).toString('hex');
|
|
212
|
-
} else {
|
|
213
|
-
return new Uint8Array(Buffer.from(message, 'hex'));
|
|
214
|
-
}
|
|
215
|
-
} else {
|
|
216
|
-
return message;
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
const formatMessage = (chainName, message)=>{
|
|
220
|
-
switch(chainName){
|
|
221
|
-
case 'EVM':
|
|
222
|
-
return formatEvmMessage(message);
|
|
223
|
-
case 'SVM':
|
|
224
|
-
return formatSolanaMessage(message);
|
|
225
|
-
case 'SUI':
|
|
226
|
-
return message;
|
|
227
|
-
default:
|
|
228
|
-
throw new Error('Unsupported chain name');
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
const getGoogleOAuthAccountId = (verifiedCredentials)=>{
|
|
232
|
-
const googleVerifiedCredential = verifiedCredentials == null ? void 0 : verifiedCredentials.find((credential)=>credential.oauthProvider === sdkApiCore.ProviderEnum.Google);
|
|
233
|
-
return googleVerifiedCredential == null ? void 0 : googleVerifiedCredential.id;
|
|
234
|
-
};
|
|
235
|
-
const createBackupData = ({ encryptedKeyShares, accountAddress, thresholdSignatureScheme, hasPassword = true })=>{
|
|
236
|
-
return {
|
|
237
|
-
keyShares: encryptedKeyShares,
|
|
238
|
-
metadata: {
|
|
239
|
-
createdAt: new Date().toISOString(),
|
|
240
|
-
accountAddress,
|
|
241
|
-
thresholdSignatureScheme,
|
|
242
|
-
hasPassword,
|
|
243
|
-
encryption: getEncryptionMetadataForVersion(ENCRYPTION_VERSION_CURRENT),
|
|
244
|
-
encryptionVersion: ENCRYPTION_VERSION_CURRENT,
|
|
245
|
-
shareCount: encryptedKeyShares.length
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
};
|
|
249
169
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
const ENCRYPTION_VERSIONS = {
|
|
257
|
-
[ENCRYPTION_VERSION_LEGACY]: {
|
|
258
|
-
version: ENCRYPTION_VERSION_LEGACY,
|
|
259
|
-
algorithm: AES_GCM_ALGORITHM,
|
|
260
|
-
keyDerivation: PBKDF2_ALGORITHM,
|
|
261
|
-
iterations: 100000,
|
|
262
|
-
hashAlgorithm: PBKDF2_HASH_ALGORITHM,
|
|
263
|
-
algorithmLength: AES_GCM_LENGTH
|
|
264
|
-
},
|
|
265
|
-
[ENCRYPTION_VERSION_CURRENT]: {
|
|
266
|
-
version: ENCRYPTION_VERSION_CURRENT,
|
|
267
|
-
algorithm: AES_GCM_ALGORITHM,
|
|
268
|
-
keyDerivation: PBKDF2_ALGORITHM,
|
|
269
|
-
iterations: 1000000,
|
|
270
|
-
hashAlgorithm: PBKDF2_HASH_ALGORITHM,
|
|
271
|
-
algorithmLength: AES_GCM_LENGTH
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
ENCRYPTION_VERSIONS[ENCRYPTION_VERSION_CURRENT].iterations;
|
|
275
|
-
ENCRYPTION_VERSIONS[ENCRYPTION_VERSION_LEGACY].iterations;
|
|
276
|
-
const getKey = async ({ password, salt, encryptionConfig })=>{
|
|
170
|
+
/**
|
|
171
|
+
* Derives a key using PBKDF2 algorithm
|
|
172
|
+
* @param params - Key derivation parameters
|
|
173
|
+
* @param encryptionConfig - Encryption configuration
|
|
174
|
+
* @returns Promise<CryptoKey>
|
|
175
|
+
*/ const derivePBKDF2Key = async ({ password, salt }, encryptionConfig)=>{
|
|
277
176
|
const passwordBytes = stringToBytes(password);
|
|
278
177
|
const initialKey = await crypto.subtle.importKey('raw', passwordBytes, {
|
|
279
178
|
name: 'PBKDF2'
|
|
@@ -293,23 +192,30 @@ const getKey = async ({ password, salt, encryptionConfig })=>{
|
|
|
293
192
|
'decrypt'
|
|
294
193
|
]);
|
|
295
194
|
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get the appropriate key derivation function based on the encryption config
|
|
198
|
+
*/ const getKey = async (params, encryptionConfig)=>{
|
|
199
|
+
// Use Argon2 for v3, PBKDF2 for v1 and v2
|
|
200
|
+
if (encryptionConfig.keyDerivation === ARGON2_ALGORITHM) {
|
|
201
|
+
return deriveArgon2Key(params, encryptionConfig);
|
|
202
|
+
} else {
|
|
203
|
+
return derivePBKDF2Key(params, encryptionConfig);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
296
206
|
/**
|
|
297
207
|
* Encrypts data using the specified encryption version.
|
|
298
208
|
* Always uses the latest encryption configuration for new encryptions by default.
|
|
299
209
|
*/ const encryptData = async ({ data, password, version = ENCRYPTION_VERSION_CURRENT })=>{
|
|
300
|
-
const encryptionConfig =
|
|
301
|
-
if (!encryptionConfig) {
|
|
302
|
-
throw new Error(`Unsupported encryption version: ${version}`);
|
|
303
|
-
}
|
|
210
|
+
const encryptionConfig = getEncryptionConfig(version);
|
|
304
211
|
try {
|
|
305
212
|
// Generate a random salt and IV
|
|
306
213
|
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
307
214
|
const iv = crypto.getRandomValues(new Uint8Array(12)); // AES-GCM requires 12 bytes
|
|
308
215
|
const key = await getKey({
|
|
309
216
|
password,
|
|
310
|
-
salt
|
|
311
|
-
|
|
312
|
-
});
|
|
217
|
+
salt
|
|
218
|
+
}, encryptionConfig);
|
|
313
219
|
// Convert the input string to bytes
|
|
314
220
|
const dataBytes = new TextEncoder().encode(data);
|
|
315
221
|
// Encrypt the data
|
|
@@ -325,13 +231,15 @@ const getKey = async ({ password, salt, encryptionConfig })=>{
|
|
|
325
231
|
version
|
|
326
232
|
};
|
|
327
233
|
} catch (error) {
|
|
328
|
-
|
|
234
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
235
|
+
throw new Error(`Error encrypting data: ${errorMessage} (version: ${version})`);
|
|
329
236
|
}
|
|
330
237
|
};
|
|
331
238
|
/**
|
|
332
239
|
* Decrypts data with version-based configuration.
|
|
333
240
|
* Uses the version field from the data to determine encryption parameters.
|
|
334
241
|
* Falls back to legacy version for backward compatibility if no version is specified.
|
|
242
|
+
* For v3 (Argon2), retries with parallelism=1 if an OperationError occurs.
|
|
335
243
|
*/ const decryptData = async ({ data, password })=>{
|
|
336
244
|
const { salt, iv, cipher, version } = data;
|
|
337
245
|
// Ensure proper base64 padding for all values
|
|
@@ -341,26 +249,42 @@ const getKey = async ({ password, salt, encryptionConfig })=>{
|
|
|
341
249
|
const saltBytes = base64ToBytes(paddedSalt);
|
|
342
250
|
const ivBytes = base64ToBytes(paddedIv);
|
|
343
251
|
const cipherBytes = base64ToBytes(paddedCipher);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (version && ENCRYPTION_VERSIONS[version]) {
|
|
347
|
-
encryptionConfig = ENCRYPTION_VERSIONS[version];
|
|
348
|
-
} else {
|
|
349
|
-
// Fallback to legacy version for backward compatibility
|
|
350
|
-
encryptionConfig = ENCRYPTION_VERSIONS[ENCRYPTION_VERSION_LEGACY];
|
|
351
|
-
}
|
|
252
|
+
// Determine which encryption configuration to use
|
|
253
|
+
const encryptionConfig = getEncryptionConfig(version);
|
|
352
254
|
try {
|
|
353
255
|
const key = await getKey({
|
|
354
256
|
password,
|
|
355
|
-
salt: saltBytes
|
|
356
|
-
|
|
357
|
-
});
|
|
257
|
+
salt: saltBytes
|
|
258
|
+
}, encryptionConfig);
|
|
358
259
|
const decryptedData = await crypto.subtle.decrypt({
|
|
359
260
|
name: AES_GCM_ALGORITHM,
|
|
360
261
|
iv: ivBytes
|
|
361
262
|
}, key, cipherBytes);
|
|
362
263
|
return new TextDecoder().decode(decryptedData);
|
|
363
264
|
} catch (error) {
|
|
265
|
+
// For a short period of time we lowered the parallelism for v3 (Argon2) to 1 to try to fix issues
|
|
266
|
+
// for users with limited resources, however this introduced a new issue that the decryption would fail
|
|
267
|
+
// for existing users with v3 (Argon2) encryption, this is a fallback for a few users.
|
|
268
|
+
if (error instanceof Error && error.name === 'OperationError' && version === 'v3' && isArgon2Config(encryptionConfig)) {
|
|
269
|
+
try {
|
|
270
|
+
// Create a modified config with parallelism=1
|
|
271
|
+
const modifiedConfig = _extends({}, encryptionConfig, {
|
|
272
|
+
parallelism: 1
|
|
273
|
+
});
|
|
274
|
+
const key = await getKey({
|
|
275
|
+
password,
|
|
276
|
+
salt: saltBytes
|
|
277
|
+
}, modifiedConfig);
|
|
278
|
+
const decryptedData = await crypto.subtle.decrypt({
|
|
279
|
+
name: AES_GCM_ALGORITHM,
|
|
280
|
+
iv: ivBytes
|
|
281
|
+
}, key, cipherBytes);
|
|
282
|
+
return new TextDecoder().decode(decryptedData);
|
|
283
|
+
} catch (retryError) {
|
|
284
|
+
// If retry also fails, throw the original error with additional context
|
|
285
|
+
throw new Error(`Decryption failed after retry with parallelism=1: ${retryError}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
364
288
|
throw new Error('Decryption failed: ' + error);
|
|
365
289
|
}
|
|
366
290
|
};
|
|
@@ -368,17 +292,21 @@ const getKey = async ({ password, salt, encryptionConfig })=>{
|
|
|
368
292
|
* Gets encryption metadata for a specific version.
|
|
369
293
|
* Used when we need to include metadata in legacy systems or APIs that require it.
|
|
370
294
|
*/ const getEncryptionMetadataForVersion = (version)=>{
|
|
371
|
-
const encryptionConfig =
|
|
372
|
-
|
|
373
|
-
throw new Error(`Unsupported encryption version: ${version}`);
|
|
374
|
-
}
|
|
375
|
-
return {
|
|
295
|
+
const encryptionConfig = getEncryptionConfig(version);
|
|
296
|
+
const metadata = {
|
|
376
297
|
algorithm: encryptionConfig.algorithm,
|
|
377
298
|
keyDerivation: encryptionConfig.keyDerivation,
|
|
378
299
|
iterations: encryptionConfig.iterations,
|
|
379
300
|
hashAlgorithm: encryptionConfig.hashAlgorithm,
|
|
380
301
|
algorithmLength: encryptionConfig.algorithmLength
|
|
381
302
|
};
|
|
303
|
+
// Add Argon2-specific metadata if applicable
|
|
304
|
+
if (isArgon2Config(encryptionConfig)) {
|
|
305
|
+
metadata.memorySize = encryptionConfig.memorySize;
|
|
306
|
+
metadata.parallelism = encryptionConfig.parallelism;
|
|
307
|
+
metadata.hashLength = encryptionConfig.hashLength;
|
|
308
|
+
}
|
|
309
|
+
return metadata;
|
|
382
310
|
};
|
|
383
311
|
|
|
384
312
|
const GOOGLE_DRIVE_UPLOAD_API = 'https://www.googleapis.com';
|
|
@@ -429,52 +357,254 @@ const uploadFileToGoogleDrive = async ({ accessToken, fileName, jsonData, parent
|
|
|
429
357
|
if (!response.ok) {
|
|
430
358
|
throw new Error('Error uploading file');
|
|
431
359
|
}
|
|
432
|
-
const result = await response.json();
|
|
433
|
-
return result; // Return file metadata, including file ID
|
|
360
|
+
const result = await response.json();
|
|
361
|
+
return result; // Return file metadata, including file ID
|
|
362
|
+
};
|
|
363
|
+
const listFilesFromGoogleDrive = async ({ accessToken, fileName })=>{
|
|
364
|
+
// Step 1: List all files inside `appDataFolder` with the specified backup filename
|
|
365
|
+
const resp = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files?q=${encodeURIComponent(`name='${fileName}'`)}&spaces=appDataFolder&orderBy=createdTime desc`, {
|
|
366
|
+
headers: {
|
|
367
|
+
Authorization: `Bearer ${accessToken}`
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
const data = await resp.json();
|
|
371
|
+
// If no files found, return null
|
|
372
|
+
if (!data.files || data.files.length === 0) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
const files = data.files;
|
|
376
|
+
return files;
|
|
377
|
+
};
|
|
378
|
+
const downloadFileFromGoogleDrive = async ({ accessToken, fileName })=>{
|
|
379
|
+
const files = await listFilesFromGoogleDrive({
|
|
380
|
+
accessToken,
|
|
381
|
+
fileName
|
|
382
|
+
});
|
|
383
|
+
if (!files || files.length === 0) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
// Get the most recent file
|
|
387
|
+
const fileMetadata = files[0];
|
|
388
|
+
// Fetch the file data using the file ID
|
|
389
|
+
const fileRes = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files/${fileMetadata.id}?alt=media`, {
|
|
390
|
+
headers: {
|
|
391
|
+
Authorization: `Bearer ${accessToken}`
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
// Read the file's raw data
|
|
395
|
+
const fileRawData = await fileRes.text();
|
|
396
|
+
if (fileRawData.length === 0) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
// Just parse and return the data without validation
|
|
401
|
+
// The client will handle validation of the structure
|
|
402
|
+
return JSON.parse(fileRawData);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const handleAxiosError = (error, message, context)=>{
|
|
409
|
+
var _error_response, _error_response1, _error_response2;
|
|
410
|
+
logger.error('[DynamicWaasWalletClient] Axios error: ', {
|
|
411
|
+
message,
|
|
412
|
+
error: (_error_response = error.response) == null ? void 0 : _error_response.data,
|
|
413
|
+
status: (_error_response1 = error.response) == null ? void 0 : _error_response1.status,
|
|
414
|
+
context
|
|
415
|
+
});
|
|
416
|
+
switch((_error_response2 = error.response) == null ? void 0 : _error_response2.status){
|
|
417
|
+
case 400:
|
|
418
|
+
throw createHttpError(400, 'Invalid request');
|
|
419
|
+
case 401:
|
|
420
|
+
throw createHttpError(401, 'Authorization header or cookie is required');
|
|
421
|
+
case 403:
|
|
422
|
+
throw createHttpError(403, 'Forbidden');
|
|
423
|
+
case 422:
|
|
424
|
+
throw createHttpError(422, 'Unprocessable content');
|
|
425
|
+
case 500:
|
|
426
|
+
throw createHttpError(500, 'Internal server error');
|
|
427
|
+
default:
|
|
428
|
+
throw createHttpError(500, 'Internal server error');
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const logger = new logger$1.Logger('DynamicWaasWalletClient', logger$1.LogLevel.DEBUG);
|
|
433
|
+
const setLoggerContext = ({ environmentId, authMode = core.AuthMode.HEADER, sessionId = undefined, userId = undefined })=>{
|
|
434
|
+
try {
|
|
435
|
+
logger$1.Logger.setEnvironmentId(environmentId);
|
|
436
|
+
logger$1.Logger.globalMetaData.set('sid', sessionId);
|
|
437
|
+
logger$1.Logger.globalMetaData.set('user_id', userId);
|
|
438
|
+
logger$1.Logger.globalMetaData.set('auth_mode', authMode);
|
|
439
|
+
} catch (error) {
|
|
440
|
+
logError({
|
|
441
|
+
message: '[DynamicWaasWalletClient] Error setting logger context',
|
|
442
|
+
error: error,
|
|
443
|
+
context: {}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
const logError = ({ message, error, context })=>{
|
|
448
|
+
if (error instanceof axios.AxiosError) {
|
|
449
|
+
handleAxiosError(error, message, context);
|
|
450
|
+
}
|
|
451
|
+
logger.error('[DynamicWaasWalletClient] Error in browser client', {
|
|
452
|
+
error: error instanceof Error ? error.message : String(error),
|
|
453
|
+
context
|
|
454
|
+
});
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const DEFAULT_LOG_LEVEL = logger$1.LogLevel.DEBUG; //todo: change back to info when done debugging
|
|
458
|
+
const STORAGE_KEY = 'dynamic-waas-wallet-client';
|
|
459
|
+
const CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX = 'dynamicWalletKeyShareBackup';
|
|
460
|
+
const SIGNED_SESSION_ID_MIN_VERSION = '4.25.4';
|
|
461
|
+
// Namespace-specific version requirements
|
|
462
|
+
const SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE = {
|
|
463
|
+
WalletKit: '4.25.4',
|
|
464
|
+
ClientSDK: '0.1.0-alpha.0'
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const isBrowser = ()=>typeof window !== 'undefined';
|
|
468
|
+
const getClientKeyShareExportFileName = ({ thresholdSignatureScheme, accountAddress })=>{
|
|
469
|
+
return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}.json`;
|
|
470
|
+
};
|
|
471
|
+
const getClientKeyShareBackupInfo = (params)=>{
|
|
472
|
+
var _params_walletProperties, _params_walletProperties_keyShares_;
|
|
473
|
+
const backups = {
|
|
474
|
+
[core.BackupLocation.DYNAMIC]: [],
|
|
475
|
+
[core.BackupLocation.GOOGLE_DRIVE]: [],
|
|
476
|
+
[core.BackupLocation.ICLOUD]: [],
|
|
477
|
+
[core.BackupLocation.USER]: [],
|
|
478
|
+
[core.BackupLocation.EXTERNAL]: [],
|
|
479
|
+
[core.BackupLocation.DELEGATED]: []
|
|
480
|
+
};
|
|
481
|
+
if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
|
|
482
|
+
return {
|
|
483
|
+
backups,
|
|
484
|
+
passwordEncrypted: false
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
params.walletProperties.keyShares.forEach((keyShare)=>{
|
|
488
|
+
if (backups[keyShare.backupLocation]) {
|
|
489
|
+
backups[keyShare.backupLocation].push({
|
|
490
|
+
location: keyShare.backupLocation,
|
|
491
|
+
keyShareId: keyShare.id,
|
|
492
|
+
externalKeyShareId: keyShare == null ? void 0 : keyShare.externalKeyShareId
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
const passwordEncrypted = Boolean((_params_walletProperties_keyShares_ = params.walletProperties.keyShares[0]) == null ? void 0 : _params_walletProperties_keyShares_.passwordEncrypted);
|
|
497
|
+
return {
|
|
498
|
+
backups,
|
|
499
|
+
passwordEncrypted
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
/**
|
|
503
|
+
* Helper function to merge keyshares and remove duplicates based on pubkey and secretShare
|
|
504
|
+
* @param existingKeyShares - Array of existing keyshares
|
|
505
|
+
* @param newKeyShares - Array of new keyshares to merge
|
|
506
|
+
* @returns Array of merged unique keyshares
|
|
507
|
+
*/ const mergeUniqueKeyShares = (existingKeyShares, newKeyShares)=>{
|
|
508
|
+
const uniqueKeyShares = newKeyShares.filter((newShare)=>!existingKeyShares.some((existingShare)=>{
|
|
509
|
+
if (!(newShare == null ? void 0 : newShare.pubkey) || !(existingShare == null ? void 0 : existingShare.pubkey)) return false;
|
|
510
|
+
return newShare.pubkey.toString() === existingShare.pubkey.toString() && newShare.secretShare === existingShare.secretShare;
|
|
511
|
+
}));
|
|
512
|
+
return [
|
|
513
|
+
...existingKeyShares,
|
|
514
|
+
...uniqueKeyShares
|
|
515
|
+
];
|
|
516
|
+
};
|
|
517
|
+
const timeoutPromise = ({ timeInMs, activity = 'Ceremony' })=>{
|
|
518
|
+
return new Promise((_, reject)=>setTimeout(()=>reject(new Error(`${activity} did not complete in ${timeInMs}ms`)), timeInMs));
|
|
519
|
+
};
|
|
520
|
+
/**
|
|
521
|
+
* Generic helper function to retry a promise-based operations
|
|
522
|
+
*
|
|
523
|
+
* @param operation - The async operation to retry
|
|
524
|
+
* @param config - Configuration options for retry behavior
|
|
525
|
+
* @returns Promise with the operation result
|
|
526
|
+
* @throws Last error encountered after all retries are exhausted
|
|
527
|
+
*/ async function retryPromise(operation, { maxAttempts = 5, retryInterval = 500, operationName = 'operation', logContext = {} } = {}) {
|
|
528
|
+
let attempts = 0;
|
|
529
|
+
while(attempts < maxAttempts){
|
|
530
|
+
try {
|
|
531
|
+
return await operation();
|
|
532
|
+
} catch (error) {
|
|
533
|
+
var _error_response;
|
|
534
|
+
attempts++;
|
|
535
|
+
logger.warn(`Failed to execute ${operationName} on attempt ${attempts}`, _extends({}, logContext, {
|
|
536
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
537
|
+
axiosError: error instanceof axios.AxiosError ? (_error_response = error.response) == null ? void 0 : _error_response.data : undefined
|
|
538
|
+
}));
|
|
539
|
+
if (attempts === maxAttempts) {
|
|
540
|
+
var _error_response1;
|
|
541
|
+
logger.error(`Failed to execute ${operationName} after ${maxAttempts} attempts`, _extends({}, logContext, {
|
|
542
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
543
|
+
axiosError: error instanceof axios.AxiosError ? (_error_response1 = error.response) == null ? void 0 : _error_response1.data : undefined
|
|
544
|
+
}));
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
// Calculate exponential backoff delay
|
|
548
|
+
const exponentialDelay = retryInterval * 2 ** (attempts - 1);
|
|
549
|
+
await new Promise((resolve)=>setTimeout(resolve, exponentialDelay));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// TypeScript needs this even though it's unreachable
|
|
553
|
+
throw new Error('Unreachable code');
|
|
554
|
+
}
|
|
555
|
+
const formatEvmMessage = (message)=>{
|
|
556
|
+
if (typeof message === 'string' && message.startsWith('0x')) {
|
|
557
|
+
const serializedTxBytes = Uint8Array.from(Buffer.from(message.slice(2), 'hex'));
|
|
558
|
+
return web.MessageHash.keccak256(serializedTxBytes);
|
|
559
|
+
}
|
|
560
|
+
return web.MessageHash.keccak256(message);
|
|
434
561
|
};
|
|
435
|
-
const
|
|
436
|
-
//
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
562
|
+
const isHexString = (str)=>{
|
|
563
|
+
// Remove 0x prefix if present
|
|
564
|
+
const hex = str.startsWith('0x') ? str.slice(2) : str;
|
|
565
|
+
// Check if string contains only hex characters
|
|
566
|
+
return /^[0-9A-Fa-f]+$/.test(hex);
|
|
567
|
+
};
|
|
568
|
+
const formatSolanaMessage = (message)=>{
|
|
569
|
+
if (typeof message === 'string') {
|
|
570
|
+
if (!isHexString(message)) {
|
|
571
|
+
return Buffer.from(message).toString('hex');
|
|
572
|
+
} else {
|
|
573
|
+
return new Uint8Array(Buffer.from(message, 'hex'));
|
|
440
574
|
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// If no files found, return null
|
|
444
|
-
if (!data.files || data.files.length === 0) {
|
|
445
|
-
return null;
|
|
575
|
+
} else {
|
|
576
|
+
return message;
|
|
446
577
|
}
|
|
447
|
-
const files = data.files;
|
|
448
|
-
return files;
|
|
449
578
|
};
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
579
|
+
const formatMessage = (chainName, message)=>{
|
|
580
|
+
switch(chainName){
|
|
581
|
+
case 'EVM':
|
|
582
|
+
return formatEvmMessage(message);
|
|
583
|
+
case 'SVM':
|
|
584
|
+
return formatSolanaMessage(message);
|
|
585
|
+
case 'SUI':
|
|
586
|
+
return message;
|
|
587
|
+
default:
|
|
588
|
+
throw new Error('Unsupported chain name');
|
|
457
589
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
590
|
+
};
|
|
591
|
+
const getGoogleOAuthAccountId = (verifiedCredentials)=>{
|
|
592
|
+
const googleVerifiedCredential = verifiedCredentials == null ? void 0 : verifiedCredentials.find((credential)=>credential.oauthProvider === sdkApiCore.ProviderEnum.Google);
|
|
593
|
+
return googleVerifiedCredential == null ? void 0 : googleVerifiedCredential.id;
|
|
594
|
+
};
|
|
595
|
+
const createBackupData = ({ encryptedKeyShares, accountAddress, thresholdSignatureScheme, hasPassword = true })=>{
|
|
596
|
+
return {
|
|
597
|
+
keyShares: encryptedKeyShares,
|
|
598
|
+
metadata: {
|
|
599
|
+
createdAt: new Date().toISOString(),
|
|
600
|
+
accountAddress,
|
|
601
|
+
thresholdSignatureScheme,
|
|
602
|
+
hasPassword,
|
|
603
|
+
encryption: getEncryptionMetadataForVersion(ENCRYPTION_VERSION_CURRENT),
|
|
604
|
+
encryptionVersion: ENCRYPTION_VERSION_CURRENT,
|
|
605
|
+
shareCount: encryptedKeyShares.length
|
|
464
606
|
}
|
|
465
|
-
}
|
|
466
|
-
// Read the file's raw data
|
|
467
|
-
const fileRawData = await fileRes.text();
|
|
468
|
-
if (fileRawData.length === 0) {
|
|
469
|
-
return null;
|
|
470
|
-
}
|
|
471
|
-
try {
|
|
472
|
-
// Just parse and return the data without validation
|
|
473
|
-
// The client will handle validation of the structure
|
|
474
|
-
return JSON.parse(fileRawData);
|
|
475
|
-
} catch (error) {
|
|
476
|
-
return null;
|
|
477
|
-
}
|
|
607
|
+
};
|
|
478
608
|
};
|
|
479
609
|
|
|
480
610
|
/**
|
|
@@ -591,6 +721,94 @@ const localStorageWriteTest = {
|
|
|
591
721
|
}
|
|
592
722
|
});
|
|
593
723
|
|
|
724
|
+
/** Algorithm label for the new hybrid encryption standard */ const ALG_LABEL_RSA = 'HYBRID-RSA-AES-256';
|
|
725
|
+
/**
|
|
726
|
+
* Convert base64 to base64url encoding
|
|
727
|
+
*/ const toBase64Url = (buffer)=>{
|
|
728
|
+
const base64 = Buffer.from(buffer).toString('base64');
|
|
729
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
730
|
+
};
|
|
731
|
+
/**
|
|
732
|
+
* Convert ArrayBuffer to base64url
|
|
733
|
+
*/ const arrayBufferToBase64Url = (buffer)=>{
|
|
734
|
+
return toBase64Url(buffer);
|
|
735
|
+
};
|
|
736
|
+
/**
|
|
737
|
+
* Import RSA public key from PEM format
|
|
738
|
+
*/ const importRSAPublicKey = async (publicKeyPem)=>{
|
|
739
|
+
// Remove PEM headers and decode base64
|
|
740
|
+
const pemHeader = '-----BEGIN PUBLIC KEY-----';
|
|
741
|
+
const pemFooter = '-----END PUBLIC KEY-----';
|
|
742
|
+
const pemContents = publicKeyPem.replace(pemHeader, '').replace(pemFooter, '').replace(/\s/g, '');
|
|
743
|
+
const binaryDer = Buffer.from(pemContents, 'base64').toString('binary');
|
|
744
|
+
const keyData = new Uint8Array(binaryDer.length);
|
|
745
|
+
for(let i = 0; i < binaryDer.length; i++){
|
|
746
|
+
keyData[i] = binaryDer.charCodeAt(i);
|
|
747
|
+
}
|
|
748
|
+
return await crypto.subtle.importKey('spki', keyData, {
|
|
749
|
+
name: 'RSA-OAEP',
|
|
750
|
+
hash: 'SHA-256'
|
|
751
|
+
}, false, [
|
|
752
|
+
'encrypt'
|
|
753
|
+
]);
|
|
754
|
+
};
|
|
755
|
+
/**
|
|
756
|
+
* Creates the encoded envelope bytes from the encrypted data components
|
|
757
|
+
*/ const createEncodedEnvelopeBytes = (iv, ciphertext, authTag, encryptedAesKey)=>{
|
|
758
|
+
const envelopeData = {
|
|
759
|
+
algorithm: ALG_LABEL_RSA,
|
|
760
|
+
iv: arrayBufferToBase64Url(iv.buffer),
|
|
761
|
+
encryptedData: arrayBufferToBase64Url(ciphertext.buffer),
|
|
762
|
+
authTag: arrayBufferToBase64Url(authTag.buffer),
|
|
763
|
+
encryptedKey: arrayBufferToBase64Url(encryptedAesKey)
|
|
764
|
+
};
|
|
765
|
+
return Buffer.from(new TextEncoder().encode(JSON.stringify(envelopeData))).toString('base64');
|
|
766
|
+
};
|
|
767
|
+
/**
|
|
768
|
+
* Encrypts data using HYBRID-RSA-AES-256 encryption scheme with Web Crypto API.
|
|
769
|
+
* 1. Generate random AES-256 key
|
|
770
|
+
* 2. Encrypt AES key with RSA public key
|
|
771
|
+
* 3. Encrypt data with AES-256-GCM
|
|
772
|
+
*/ const encryptDelegatedKeyShare = async (data, publicKeyPem)=>{
|
|
773
|
+
try {
|
|
774
|
+
// Step 1: Generate a random AES-256 key and 16-byte IV
|
|
775
|
+
const aesKey = await crypto.subtle.generateKey({
|
|
776
|
+
name: 'AES-GCM',
|
|
777
|
+
length: 256
|
|
778
|
+
}, true, [
|
|
779
|
+
'encrypt'
|
|
780
|
+
]);
|
|
781
|
+
const iv = crypto.getRandomValues(new Uint8Array(16)); // 128-bit IV for GCM
|
|
782
|
+
// Step 2: Encrypt the data with AES-256-GCM
|
|
783
|
+
const plaintext = new TextEncoder().encode(data);
|
|
784
|
+
const encryptedData = await crypto.subtle.encrypt({
|
|
785
|
+
name: 'AES-GCM',
|
|
786
|
+
iv: iv
|
|
787
|
+
}, aesKey, plaintext);
|
|
788
|
+
// Extract the auth tag from the encrypted data (last 16 bytes)
|
|
789
|
+
const encryptedDataArray = new Uint8Array(encryptedData);
|
|
790
|
+
const authTag = encryptedDataArray.slice(-16);
|
|
791
|
+
const ciphertext = encryptedDataArray.slice(0, -16);
|
|
792
|
+
// Step 3: Encrypt the AES key with RSA public key
|
|
793
|
+
const rsaPublicKey = await importRSAPublicKey(publicKeyPem);
|
|
794
|
+
// Export the AES key to encrypt it
|
|
795
|
+
const aesKeyData = await crypto.subtle.exportKey('raw', aesKey);
|
|
796
|
+
const encryptedAesKey = await crypto.subtle.encrypt({
|
|
797
|
+
name: 'RSA-OAEP'
|
|
798
|
+
}, rsaPublicKey, aesKeyData);
|
|
799
|
+
return {
|
|
800
|
+
algorithm: ALG_LABEL_RSA,
|
|
801
|
+
iv: arrayBufferToBase64Url(iv.buffer),
|
|
802
|
+
encryptedData: arrayBufferToBase64Url(ciphertext.buffer),
|
|
803
|
+
authTag: arrayBufferToBase64Url(authTag.buffer),
|
|
804
|
+
encryptedKey: arrayBufferToBase64Url(encryptedAesKey),
|
|
805
|
+
encodedEnvelopeBytes: createEncodedEnvelopeBytes(iv, ciphertext, authTag, encryptedAesKey)
|
|
806
|
+
};
|
|
807
|
+
} catch (error) {
|
|
808
|
+
throw new Error(`Encryption failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
|
|
594
812
|
class DynamicWalletClient {
|
|
595
813
|
getAuthMode() {
|
|
596
814
|
return this.authMode;
|
|
@@ -656,7 +874,7 @@ class DynamicWalletClient {
|
|
|
656
874
|
this.sessionId = sessionId;
|
|
657
875
|
} catch (error) {
|
|
658
876
|
logError({
|
|
659
|
-
message: '
|
|
877
|
+
message: 'Error initializing logger context',
|
|
660
878
|
error: error,
|
|
661
879
|
context: {}
|
|
662
880
|
});
|
|
@@ -813,7 +1031,7 @@ class DynamicWalletClient {
|
|
|
813
1031
|
};
|
|
814
1032
|
} catch (error) {
|
|
815
1033
|
logError({
|
|
816
|
-
message: '
|
|
1034
|
+
message: 'Error in keyGen',
|
|
817
1035
|
error: error,
|
|
818
1036
|
context: {
|
|
819
1037
|
chainName,
|
|
@@ -893,7 +1111,7 @@ class DynamicWalletClient {
|
|
|
893
1111
|
};
|
|
894
1112
|
} catch (error) {
|
|
895
1113
|
logError({
|
|
896
|
-
message: '
|
|
1114
|
+
message: 'Error in importRawPrivateKey',
|
|
897
1115
|
error: error,
|
|
898
1116
|
context: {
|
|
899
1117
|
chainName,
|
|
@@ -904,7 +1122,7 @@ class DynamicWalletClient {
|
|
|
904
1122
|
throw error;
|
|
905
1123
|
}
|
|
906
1124
|
}
|
|
907
|
-
async serverSign({ walletId, message, isFormatted, mfaToken, context, onError }) {
|
|
1125
|
+
async serverSign({ walletId, message, isFormatted, mfaToken, context, onError, dynamicRequestId }) {
|
|
908
1126
|
// Create the room and sign the message
|
|
909
1127
|
if (typeof message !== 'string') {
|
|
910
1128
|
message = `0x${Buffer.from(message).toString('hex')}`;
|
|
@@ -913,14 +1131,14 @@ class DynamicWalletClient {
|
|
|
913
1131
|
walletId,
|
|
914
1132
|
message,
|
|
915
1133
|
isFormatted,
|
|
916
|
-
dynamicRequestId
|
|
1134
|
+
dynamicRequestId,
|
|
917
1135
|
mfaToken,
|
|
918
1136
|
context: context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined,
|
|
919
1137
|
onError
|
|
920
1138
|
});
|
|
921
1139
|
return data;
|
|
922
1140
|
}
|
|
923
|
-
async clientSign({ chainName, message, roomId, keyShare, derivationPath, isFormatted }) {
|
|
1141
|
+
async clientSign({ chainName, message, roomId, keyShare, derivationPath, isFormatted, dynamicRequestId }) {
|
|
924
1142
|
try {
|
|
925
1143
|
const mpcSigner = getMPCSigner({
|
|
926
1144
|
chainName,
|
|
@@ -938,13 +1156,14 @@ class DynamicWalletClient {
|
|
|
938
1156
|
return signature;
|
|
939
1157
|
} catch (error) {
|
|
940
1158
|
logError({
|
|
941
|
-
message: '
|
|
1159
|
+
message: 'Error in clientSign',
|
|
942
1160
|
error: error,
|
|
943
1161
|
context: {
|
|
944
1162
|
chainName,
|
|
945
1163
|
roomId,
|
|
946
1164
|
derivationPath,
|
|
947
|
-
isFormatted
|
|
1165
|
+
isFormatted,
|
|
1166
|
+
dynamicRequestId
|
|
948
1167
|
}
|
|
949
1168
|
});
|
|
950
1169
|
throw error;
|
|
@@ -952,6 +1171,7 @@ class DynamicWalletClient {
|
|
|
952
1171
|
}
|
|
953
1172
|
//todo: need to modify with imported flag
|
|
954
1173
|
async sign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, context, onError }) {
|
|
1174
|
+
const dynamicRequestId = uuid.v4();
|
|
955
1175
|
try {
|
|
956
1176
|
await this.verifyPassword({
|
|
957
1177
|
accountAddress,
|
|
@@ -972,13 +1192,15 @@ class DynamicWalletClient {
|
|
|
972
1192
|
isFormatted,
|
|
973
1193
|
mfaToken,
|
|
974
1194
|
context,
|
|
975
|
-
onError
|
|
1195
|
+
onError,
|
|
1196
|
+
dynamicRequestId
|
|
976
1197
|
});
|
|
977
1198
|
this.logger.debug('[DynamicWaasWalletClient] Server sign completed', {
|
|
978
1199
|
message,
|
|
979
1200
|
accountAddress,
|
|
980
1201
|
walletId: wallet.walletId,
|
|
981
|
-
roomId: data.roomId
|
|
1202
|
+
roomId: data.roomId,
|
|
1203
|
+
dynamicRequestId
|
|
982
1204
|
});
|
|
983
1205
|
const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
|
|
984
1206
|
// Perform the client sign and return the signature
|
|
@@ -991,7 +1213,8 @@ class DynamicWalletClient {
|
|
|
991
1213
|
roomId: data.roomId,
|
|
992
1214
|
keyShare: clientKeyShares[0],
|
|
993
1215
|
derivationPath,
|
|
994
|
-
isFormatted
|
|
1216
|
+
isFormatted,
|
|
1217
|
+
dynamicRequestId
|
|
995
1218
|
});
|
|
996
1219
|
this.logger.debug('[DynamicWaasWalletClient] Client sign completed', {
|
|
997
1220
|
chainName,
|
|
@@ -1003,12 +1226,13 @@ class DynamicWalletClient {
|
|
|
1003
1226
|
return signature;
|
|
1004
1227
|
} catch (error) {
|
|
1005
1228
|
logError({
|
|
1006
|
-
message: '
|
|
1229
|
+
message: 'Error in sign',
|
|
1007
1230
|
error: error,
|
|
1008
1231
|
context: {
|
|
1009
1232
|
accountAddress,
|
|
1010
1233
|
chainName,
|
|
1011
|
-
isFormatted: isFormatted ? 'true' : 'false'
|
|
1234
|
+
isFormatted: isFormatted ? 'true' : 'false',
|
|
1235
|
+
dynamicRequestId
|
|
1012
1236
|
}
|
|
1013
1237
|
});
|
|
1014
1238
|
throw error;
|
|
@@ -1056,7 +1280,7 @@ class DynamicWalletClient {
|
|
|
1056
1280
|
});
|
|
1057
1281
|
} catch (error) {
|
|
1058
1282
|
logError({
|
|
1059
|
-
message: '
|
|
1283
|
+
message: 'Error in refreshWalletAccountShares',
|
|
1060
1284
|
error: error,
|
|
1061
1285
|
context: {
|
|
1062
1286
|
accountAddress,
|
|
@@ -1121,6 +1345,7 @@ class DynamicWalletClient {
|
|
|
1121
1345
|
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, backupToGoogleDrive = false, delegateToProjectEnvironment = false, mfaToken }) {
|
|
1122
1346
|
const dynamicRequestId = uuid.v4();
|
|
1123
1347
|
try {
|
|
1348
|
+
var _publicKey_key, _publicKey_key1;
|
|
1124
1349
|
await this.verifyPassword({
|
|
1125
1350
|
accountAddress,
|
|
1126
1351
|
password,
|
|
@@ -1172,35 +1397,54 @@ class DynamicWalletClient {
|
|
|
1172
1397
|
chainName,
|
|
1173
1398
|
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1174
1399
|
});
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1177
|
-
|
|
1400
|
+
const existingResharePromises = existingClientKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds));
|
|
1401
|
+
const newResharePromises = newClientInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds));
|
|
1402
|
+
// Run both share parties in parallel by group
|
|
1403
|
+
const [existingReshareResults, newReshareResults] = await Promise.all([
|
|
1404
|
+
Promise.all(existingResharePromises),
|
|
1405
|
+
Promise.all(newResharePromises)
|
|
1178
1406
|
]);
|
|
1407
|
+
const clientKeyshares = delegateToProjectEnvironment ? [
|
|
1408
|
+
...existingReshareResults
|
|
1409
|
+
] : [
|
|
1410
|
+
...existingReshareResults,
|
|
1411
|
+
...newReshareResults
|
|
1412
|
+
];
|
|
1179
1413
|
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1180
1414
|
thresholdSignatureScheme: newThresholdSignatureScheme
|
|
1181
1415
|
});
|
|
1182
1416
|
await this.setClientKeySharesToLocalStorage({
|
|
1183
1417
|
accountAddress,
|
|
1184
|
-
clientKeyShares:
|
|
1418
|
+
clientKeyShares: clientKeyshares,
|
|
1185
1419
|
overwriteOrMerge: 'overwrite'
|
|
1186
1420
|
});
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1421
|
+
await this.storeEncryptedBackupByWallet({
|
|
1422
|
+
accountAddress,
|
|
1423
|
+
password,
|
|
1424
|
+
signedSessionId,
|
|
1425
|
+
backupToGoogleDrive,
|
|
1426
|
+
delegatedLocations: newReshareResults.map(()=>({
|
|
1427
|
+
location: core.BackupLocation.DELEGATED
|
|
1428
|
+
}))
|
|
1429
|
+
});
|
|
1430
|
+
const publicKey = await this.apiClient.getDelegatedEncryptionKey({
|
|
1431
|
+
environmentId: this.environmentId
|
|
1432
|
+
});
|
|
1433
|
+
if (!(publicKey == null ? void 0 : (_publicKey_key = publicKey.key) == null ? void 0 : _publicKey_key.publicKeyPemB64)) {
|
|
1434
|
+
throw new Error('Public key not found');
|
|
1200
1435
|
}
|
|
1436
|
+
const encryptedDelegatedKeyShareEnvelope = await encryptDelegatedKeyShare(JSON.stringify(clientKeyshares[0]), publicKey == null ? void 0 : (_publicKey_key1 = publicKey.key) == null ? void 0 : _publicKey_key1.publicKeyPemB64);
|
|
1437
|
+
await this.apiClient.publishDelegatedKeyShare({
|
|
1438
|
+
walletId: this.walletMap[accountAddress].walletId,
|
|
1439
|
+
encryptedKeyShare: encryptedDelegatedKeyShareEnvelope == null ? void 0 : encryptedDelegatedKeyShareEnvelope.encodedEnvelopeBytes,
|
|
1440
|
+
encryptionVersion: ENCRYPTION_VERSION_CURRENT,
|
|
1441
|
+
signedSessionId,
|
|
1442
|
+
requiresSignedSessionId: this.requiresSignedSessionId(),
|
|
1443
|
+
dynamicRequestId
|
|
1444
|
+
});
|
|
1201
1445
|
} catch (error) {
|
|
1202
1446
|
logError({
|
|
1203
|
-
message: '
|
|
1447
|
+
message: 'Error in reshare, resetting wallet to previous state',
|
|
1204
1448
|
error: error,
|
|
1205
1449
|
context: {
|
|
1206
1450
|
accountAddress,
|
|
@@ -1211,36 +1455,14 @@ class DynamicWalletClient {
|
|
|
1211
1455
|
dynamicRequestId
|
|
1212
1456
|
}
|
|
1213
1457
|
});
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
async sendKeySharesToDelegatedAccess({ accountAddress, chainName, delegatedKeyShares, environmentId, userId, walletId }) {
|
|
1218
|
-
try {
|
|
1219
|
-
if (!this.delegatedAccessEndpoint) {
|
|
1220
|
-
throw new Error('Cannot send key shares to delegated access because delegated access endpoint is not set');
|
|
1221
|
-
}
|
|
1222
|
-
const delegatedAccessEndpoint = this.delegatedAccessEndpoint;
|
|
1223
|
-
const response = await fetch(delegatedAccessEndpoint, {
|
|
1224
|
-
method: 'POST',
|
|
1225
|
-
body: JSON.stringify({
|
|
1226
|
-
chainName,
|
|
1227
|
-
accountAddress,
|
|
1228
|
-
delegatedKeyShares,
|
|
1229
|
-
environmentId,
|
|
1230
|
-
userId,
|
|
1231
|
-
walletId
|
|
1232
|
-
})
|
|
1458
|
+
// reset user wallet when reshare fails, this would allow the client to recover wallets from an active state
|
|
1459
|
+
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1460
|
+
thresholdSignatureScheme: oldThresholdSignatureScheme
|
|
1233
1461
|
});
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
message: '[DynamicWaasWalletClient] Error in sendKeySharesToDelegatedAccess',
|
|
1239
|
-
error: error,
|
|
1240
|
-
context: {
|
|
1241
|
-
accountAddress,
|
|
1242
|
-
chainName
|
|
1243
|
-
}
|
|
1462
|
+
await this.setClientKeySharesToLocalStorage({
|
|
1463
|
+
accountAddress,
|
|
1464
|
+
clientKeyShares: [],
|
|
1465
|
+
overwriteOrMerge: 'overwrite'
|
|
1244
1466
|
});
|
|
1245
1467
|
throw error;
|
|
1246
1468
|
}
|
|
@@ -1251,15 +1473,9 @@ class DynamicWalletClient {
|
|
|
1251
1473
|
if (!delegateToProjectEnvironment) {
|
|
1252
1474
|
throw new Error('Delegation is not allowed for this project environment');
|
|
1253
1475
|
}
|
|
1254
|
-
const environmentSettings = await this.apiClient.getEnvironmentSettings();
|
|
1255
|
-
const delegatedAccessEndpoint = environmentSettings.sdk.waas.delegatedAccessEndpoint;
|
|
1256
|
-
if (!delegatedAccessEndpoint) {
|
|
1257
|
-
throw new Error('Cannot delegate key shares because verified access endpoint is not set in the environment settings');
|
|
1258
|
-
}
|
|
1259
|
-
this.delegatedAccessEndpoint = delegatedAccessEndpoint;
|
|
1260
1476
|
const wallet = await this.getWallet({
|
|
1261
1477
|
accountAddress,
|
|
1262
|
-
walletOperation: core.WalletOperation.
|
|
1478
|
+
walletOperation: core.WalletOperation.RESHARE,
|
|
1263
1479
|
password,
|
|
1264
1480
|
signedSessionId
|
|
1265
1481
|
});
|
|
@@ -1267,28 +1483,23 @@ class DynamicWalletClient {
|
|
|
1267
1483
|
throw new Error('Delegation is not allowed for SUI');
|
|
1268
1484
|
}
|
|
1269
1485
|
const currentThresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
const delegatedKeyShares = backupInfo.backups[core.BackupLocation.EXTERNAL] || [];
|
|
1285
|
-
return delegatedKeyShares;
|
|
1286
|
-
} else {
|
|
1287
|
-
throw new Error('Delegation is not allowed for this threshold signature scheme');
|
|
1288
|
-
}
|
|
1486
|
+
await this.reshare({
|
|
1487
|
+
chainName: this.walletMap[accountAddress].chainName,
|
|
1488
|
+
accountAddress,
|
|
1489
|
+
oldThresholdSignatureScheme: currentThresholdSignatureScheme,
|
|
1490
|
+
newThresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_THREE,
|
|
1491
|
+
password,
|
|
1492
|
+
signedSessionId,
|
|
1493
|
+
backupToGoogleDrive: false,
|
|
1494
|
+
delegateToProjectEnvironment: true,
|
|
1495
|
+
mfaToken
|
|
1496
|
+
});
|
|
1497
|
+
const backupInfo = this.walletMap[accountAddress].clientKeySharesBackupInfo;
|
|
1498
|
+
const delegatedKeyShares = backupInfo.backups[core.BackupLocation.DELEGATED] || [];
|
|
1499
|
+
return delegatedKeyShares;
|
|
1289
1500
|
} catch (error) {
|
|
1290
1501
|
logError({
|
|
1291
|
-
message: '
|
|
1502
|
+
message: 'Error in delegateKeyShares',
|
|
1292
1503
|
error: error,
|
|
1293
1504
|
context: {
|
|
1294
1505
|
accountAddress
|
|
@@ -1355,7 +1566,7 @@ class DynamicWalletClient {
|
|
|
1355
1566
|
};
|
|
1356
1567
|
} catch (error) {
|
|
1357
1568
|
logError({
|
|
1358
|
-
message: '
|
|
1569
|
+
message: 'Error in exportKey',
|
|
1359
1570
|
error: error,
|
|
1360
1571
|
context: {
|
|
1361
1572
|
accountAddress,
|
|
@@ -1403,7 +1614,7 @@ class DynamicWalletClient {
|
|
|
1403
1614
|
};
|
|
1404
1615
|
} catch (error) {
|
|
1405
1616
|
logError({
|
|
1406
|
-
message: '
|
|
1617
|
+
message: 'Error in offlineExportKey',
|
|
1407
1618
|
error: error,
|
|
1408
1619
|
context: {
|
|
1409
1620
|
chainName
|
|
@@ -1441,7 +1652,7 @@ class DynamicWalletClient {
|
|
|
1441
1652
|
return (parsedWalletObject == null ? void 0 : parsedWalletObject.clientKeyShares) || [];
|
|
1442
1653
|
} catch (error) {
|
|
1443
1654
|
logError({
|
|
1444
|
-
message: `
|
|
1655
|
+
message: `Error parsing clientKeyShares: Error for accountAddress:`,
|
|
1445
1656
|
error: error,
|
|
1446
1657
|
context: {
|
|
1447
1658
|
accountAddress
|
|
@@ -1487,7 +1698,8 @@ class DynamicWalletClient {
|
|
|
1487
1698
|
* @param params.signedSessionId - Optional signed session ID for authentication
|
|
1488
1699
|
* @param params.backupToGoogleDrive - Whether to backup to Google Drive (defaults to false)
|
|
1489
1700
|
* @returns Promise with backup metadata including share locations and IDs
|
|
1490
|
-
*/ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined, signedSessionId, backupToGoogleDrive = false }) {
|
|
1701
|
+
*/ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined, signedSessionId, backupToGoogleDrive = false, delegatedLocations = [] }) {
|
|
1702
|
+
const dynamicRequestId = uuid.v4();
|
|
1491
1703
|
try {
|
|
1492
1704
|
var _this_walletMap_accountAddress, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_GOOGLE_DRIVE, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups, _this_walletMap_accountAddress_clientKeySharesBackupInfo, _this_walletMap_accountAddress1;
|
|
1493
1705
|
const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromLocalStorage({
|
|
@@ -1496,7 +1708,7 @@ class DynamicWalletClient {
|
|
|
1496
1708
|
if (!((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.walletId)) {
|
|
1497
1709
|
const error = new Error(`WalletId not found for accountAddress ${accountAddress}`);
|
|
1498
1710
|
logError({
|
|
1499
|
-
message: '
|
|
1711
|
+
message: 'Error in storeEncryptedBackupByWallet, wallet or walletId not found from the wallet map',
|
|
1500
1712
|
error,
|
|
1501
1713
|
context: {
|
|
1502
1714
|
accountAddress,
|
|
@@ -1533,7 +1745,8 @@ class DynamicWalletClient {
|
|
|
1533
1745
|
encryptionVersion: ENCRYPTION_VERSION_CURRENT,
|
|
1534
1746
|
signedSessionId,
|
|
1535
1747
|
authMode: this.authMode,
|
|
1536
|
-
requiresSignedSessionId: this.requiresSignedSessionId()
|
|
1748
|
+
requiresSignedSessionId: this.requiresSignedSessionId(),
|
|
1749
|
+
dynamicRequestId
|
|
1537
1750
|
});
|
|
1538
1751
|
if (data.keyShareIds.length === 0) {
|
|
1539
1752
|
throw new Error('No key shares were backed up');
|
|
@@ -1553,9 +1766,13 @@ class DynamicWalletClient {
|
|
|
1553
1766
|
location: core.BackupLocation.GOOGLE_DRIVE
|
|
1554
1767
|
});
|
|
1555
1768
|
}
|
|
1769
|
+
if ((delegatedLocations == null ? void 0 : delegatedLocations.length) > 0) {
|
|
1770
|
+
locations.push(...delegatedLocations);
|
|
1771
|
+
}
|
|
1556
1772
|
const backupData = await this.apiClient.markKeySharesAsBackedUp({
|
|
1557
1773
|
walletId: this.walletMap[accountAddress].walletId,
|
|
1558
|
-
locations
|
|
1774
|
+
locations,
|
|
1775
|
+
dynamicRequestId
|
|
1559
1776
|
});
|
|
1560
1777
|
const updatedBackupInfo = getClientKeyShareBackupInfo({
|
|
1561
1778
|
walletProperties: {
|
|
@@ -1576,109 +1793,11 @@ class DynamicWalletClient {
|
|
|
1576
1793
|
return data;
|
|
1577
1794
|
} catch (error) {
|
|
1578
1795
|
logError({
|
|
1579
|
-
message: '
|
|
1796
|
+
message: 'Error in storeEncryptedBackupByWallet',
|
|
1580
1797
|
error: error,
|
|
1581
1798
|
context: {
|
|
1582
|
-
accountAddress
|
|
1583
|
-
}
|
|
1584
|
-
});
|
|
1585
|
-
throw error;
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
async storeEncryptedBackupByDelegatedWallet({ accountAddress, clientKeyShares = undefined, password = undefined, signedSessionId }) {
|
|
1589
|
-
try {
|
|
1590
|
-
var _this_walletMap_accountAddress, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_EXTERNAL, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups, _this_walletMap_accountAddress_clientKeySharesBackupInfo, _this_walletMap_accountAddress1;
|
|
1591
|
-
const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromLocalStorage({
|
|
1592
|
-
accountAddress
|
|
1593
|
-
});
|
|
1594
|
-
if (!((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.walletId)) {
|
|
1595
|
-
const error = new Error(`WalletId not found for accountAddress ${accountAddress}`);
|
|
1596
|
-
logError({
|
|
1597
|
-
message: '[DynamicWaasWalletClient] Error in storeEncryptedBackupByWallet, wallet or walletId not found from the wallet map',
|
|
1598
|
-
error,
|
|
1599
|
-
context: {
|
|
1600
|
-
accountAddress,
|
|
1601
|
-
walletMap: this.walletMap
|
|
1602
|
-
}
|
|
1603
|
-
});
|
|
1604
|
-
throw error;
|
|
1605
|
-
}
|
|
1606
|
-
let dynamicClientKeyShares = [];
|
|
1607
|
-
let delegatedKeyShares = [];
|
|
1608
|
-
const encryptedKeyShares = await Promise.all(keySharesToBackup.map((keyShare)=>this.encryptKeyShare({
|
|
1609
|
-
keyShare,
|
|
1610
|
-
password
|
|
1611
|
-
})));
|
|
1612
|
-
const hasExistingDelegatedBackup = ((_this_walletMap_accountAddress1 = this.walletMap[accountAddress]) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo = _this_walletMap_accountAddress1.clientKeySharesBackupInfo) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups = _this_walletMap_accountAddress_clientKeySharesBackupInfo.backups) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_EXTERNAL = _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups[core.BackupLocation.EXTERNAL]) == null ? void 0 : _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_EXTERNAL.length) > 0;
|
|
1613
|
-
const shouldBackupToDelegated = hasExistingDelegatedBackup || keySharesToBackup.length >= 2;
|
|
1614
|
-
if (shouldBackupToDelegated) {
|
|
1615
|
-
// For 2 shares: 1 to backend, 1 to delegated
|
|
1616
|
-
// For 3+ shares: N-1 to backend, 1 to delegated
|
|
1617
|
-
dynamicClientKeyShares = encryptedKeyShares.slice(0, -core.DELEGATED_SHARE_COUNT);
|
|
1618
|
-
delegatedKeyShares = encryptedKeyShares.slice(-core.DELEGATED_SHARE_COUNT);
|
|
1619
|
-
} else {
|
|
1620
|
-
dynamicClientKeyShares = encryptedKeyShares;
|
|
1621
|
-
}
|
|
1622
|
-
const data = await this.apiClient.storeEncryptedBackupByWallet({
|
|
1623
|
-
walletId: this.walletMap[accountAddress].walletId,
|
|
1624
|
-
encryptedKeyShares: dynamicClientKeyShares,
|
|
1625
|
-
passwordEncrypted: Boolean(password) && password !== this.environmentId,
|
|
1626
|
-
encryptionVersion: ENCRYPTION_VERSION_CURRENT,
|
|
1627
|
-
signedSessionId,
|
|
1628
|
-
authMode: this.authMode,
|
|
1629
|
-
requiresSignedSessionId: this.requiresSignedSessionId()
|
|
1630
|
-
});
|
|
1631
|
-
await this.apiClient.markKeySharesAsBackedUp({
|
|
1632
|
-
walletId: this.walletMap[accountAddress].walletId,
|
|
1633
|
-
locations: [
|
|
1634
|
-
{
|
|
1635
|
-
location: core.BackupLocation.DYNAMIC
|
|
1636
|
-
}
|
|
1637
|
-
]
|
|
1638
|
-
});
|
|
1639
|
-
if (delegatedKeyShares.length > 0) {
|
|
1640
|
-
const wallet = this.walletMap[accountAddress];
|
|
1641
|
-
var _this_userId;
|
|
1642
|
-
const delegatedKeyShareIds = await this.sendKeySharesToDelegatedAccess({
|
|
1643
1799
|
accountAddress,
|
|
1644
|
-
|
|
1645
|
-
environmentId: this.environmentId,
|
|
1646
|
-
walletId: wallet.walletId,
|
|
1647
|
-
userId: (_this_userId = this.userId) != null ? _this_userId : '',
|
|
1648
|
-
delegatedKeyShares: delegatedKeyShares
|
|
1649
|
-
});
|
|
1650
|
-
data.keyShares.push({
|
|
1651
|
-
backupLocation: core.BackupLocation.EXTERNAL,
|
|
1652
|
-
id: delegatedKeyShareIds
|
|
1653
|
-
});
|
|
1654
|
-
//todo: combine with user share service backup once other branch is merged
|
|
1655
|
-
await this.apiClient.markKeySharesAsBackedUp({
|
|
1656
|
-
walletId: this.walletMap[accountAddress].walletId,
|
|
1657
|
-
locations: [
|
|
1658
|
-
{
|
|
1659
|
-
location: core.BackupLocation.EXTERNAL
|
|
1660
|
-
}
|
|
1661
|
-
]
|
|
1662
|
-
});
|
|
1663
|
-
}
|
|
1664
|
-
const updatedBackupInfo = getClientKeyShareBackupInfo({
|
|
1665
|
-
walletProperties: {
|
|
1666
|
-
derivationPath: this.walletMap[accountAddress].derivationPath,
|
|
1667
|
-
keyShares: data.keyShares,
|
|
1668
|
-
thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
|
|
1669
|
-
}
|
|
1670
|
-
});
|
|
1671
|
-
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1672
|
-
clientKeySharesBackupInfo: updatedBackupInfo
|
|
1673
|
-
});
|
|
1674
|
-
await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
|
|
1675
|
-
return data;
|
|
1676
|
-
} catch (error) {
|
|
1677
|
-
logError({
|
|
1678
|
-
message: '[DynamicWaasWalletClient] Error in storeEncryptedBackupByDelegatedWallet',
|
|
1679
|
-
error: error,
|
|
1680
|
-
context: {
|
|
1681
|
-
accountAddress
|
|
1800
|
+
dynamicRequestId
|
|
1682
1801
|
}
|
|
1683
1802
|
});
|
|
1684
1803
|
throw error;
|
|
@@ -1725,8 +1844,9 @@ class DynamicWalletClient {
|
|
|
1725
1844
|
* @returns The Google OAuth Account ID
|
|
1726
1845
|
* @throws Error if no Google OAuth account ID is found
|
|
1727
1846
|
*/ async getGoogleOauthAccountIdOrThrow(accountAddress) {
|
|
1847
|
+
const dynamicRequestId = uuid.v4();
|
|
1728
1848
|
try {
|
|
1729
|
-
const user = await this.apiClient.getUser();
|
|
1849
|
+
const user = await this.apiClient.getUser(dynamicRequestId);
|
|
1730
1850
|
const oauthAccountId = getGoogleOAuthAccountId(user == null ? void 0 : user.verifiedCredentials);
|
|
1731
1851
|
if (!oauthAccountId) {
|
|
1732
1852
|
const error = new Error('No Google OAuth account ID found');
|
|
@@ -1743,10 +1863,11 @@ class DynamicWalletClient {
|
|
|
1743
1863
|
return oauthAccountId;
|
|
1744
1864
|
} catch (error) {
|
|
1745
1865
|
logError({
|
|
1746
|
-
message: '
|
|
1866
|
+
message: 'Error in getGoogleOauthAccountIdOrThrow',
|
|
1747
1867
|
error: error,
|
|
1748
1868
|
context: {
|
|
1749
|
-
accountAddress
|
|
1869
|
+
accountAddress,
|
|
1870
|
+
dynamicRequestId
|
|
1750
1871
|
}
|
|
1751
1872
|
});
|
|
1752
1873
|
throw error;
|
|
@@ -1816,7 +1937,7 @@ class DynamicWalletClient {
|
|
|
1816
1937
|
return decryptedKeyShares;
|
|
1817
1938
|
} catch (error) {
|
|
1818
1939
|
logError({
|
|
1819
|
-
message: '
|
|
1940
|
+
message: 'Error in recoverEncryptedBackupByWallet',
|
|
1820
1941
|
error: error,
|
|
1821
1942
|
context: {
|
|
1822
1943
|
accountAddress,
|
|
@@ -1882,7 +2003,7 @@ class DynamicWalletClient {
|
|
|
1882
2003
|
}
|
|
1883
2004
|
} catch (error) {
|
|
1884
2005
|
logError({
|
|
1885
|
-
message: '
|
|
2006
|
+
message: 'Error in backupKeySharesToGoogleDrive',
|
|
1886
2007
|
error: error,
|
|
1887
2008
|
context: {
|
|
1888
2009
|
accountAddress
|
|
@@ -1930,7 +2051,7 @@ class DynamicWalletClient {
|
|
|
1930
2051
|
return;
|
|
1931
2052
|
} catch (error) {
|
|
1932
2053
|
logError({
|
|
1933
|
-
message: '
|
|
2054
|
+
message: 'Error in backupKeySharesToGoogleDrive',
|
|
1934
2055
|
error: error,
|
|
1935
2056
|
context: {
|
|
1936
2057
|
accountAddress
|
|
@@ -1962,7 +2083,7 @@ class DynamicWalletClient {
|
|
|
1962
2083
|
}));
|
|
1963
2084
|
} catch (error) {
|
|
1964
2085
|
logError({
|
|
1965
|
-
message: '
|
|
2086
|
+
message: 'Failed to download backup from Google Drive',
|
|
1966
2087
|
error: error,
|
|
1967
2088
|
context: {
|
|
1968
2089
|
accountAddress,
|
|
@@ -1974,7 +2095,7 @@ class DynamicWalletClient {
|
|
|
1974
2095
|
if (!backupData) {
|
|
1975
2096
|
const error = new Error('No backup file found');
|
|
1976
2097
|
logError({
|
|
1977
|
-
message: '
|
|
2098
|
+
message: 'No backup file found',
|
|
1978
2099
|
error: new Error('No backup file found'),
|
|
1979
2100
|
context: {
|
|
1980
2101
|
accountAddress,
|
|
@@ -1987,7 +2108,7 @@ class DynamicWalletClient {
|
|
|
1987
2108
|
if (!backupData.keyShares || !backupData.metadata) {
|
|
1988
2109
|
const error = new Error('Invalid backup format: missing keyShares or metadata');
|
|
1989
2110
|
logError({
|
|
1990
|
-
message: '
|
|
2111
|
+
message: 'Invalid backup format: missing keyShares or metadata',
|
|
1991
2112
|
error,
|
|
1992
2113
|
context: {
|
|
1993
2114
|
accountAddress,
|
|
@@ -2009,7 +2130,7 @@ class DynamicWalletClient {
|
|
|
2009
2130
|
return decryptedKeyShares;
|
|
2010
2131
|
} catch (error) {
|
|
2011
2132
|
logError({
|
|
2012
|
-
message: '
|
|
2133
|
+
message: 'Error in restoreBackupFromGoogleDrive',
|
|
2013
2134
|
error: error,
|
|
2014
2135
|
context: {
|
|
2015
2136
|
accountAddress
|
|
@@ -2141,7 +2262,7 @@ class DynamicWalletClient {
|
|
|
2141
2262
|
});
|
|
2142
2263
|
} catch (error) {
|
|
2143
2264
|
logError({
|
|
2144
|
-
message: '
|
|
2265
|
+
message: 'Error in verifyPassword',
|
|
2145
2266
|
error: error,
|
|
2146
2267
|
context: {
|
|
2147
2268
|
accountAddress
|
|
@@ -2193,6 +2314,7 @@ class DynamicWalletClient {
|
|
|
2193
2314
|
return true;
|
|
2194
2315
|
}
|
|
2195
2316
|
async getWalletClientKeyShareBackupInfo({ accountAddress }) {
|
|
2317
|
+
const dynamicRequestId = uuid.v4();
|
|
2196
2318
|
try {
|
|
2197
2319
|
var _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups, _this_walletMap_accountAddress_clientKeySharesBackupInfo, _this_walletMap_accountAddress, _user_verifiedCredentials;
|
|
2198
2320
|
// Return existing backup info if it exists
|
|
@@ -2200,23 +2322,25 @@ class DynamicWalletClient {
|
|
|
2200
2322
|
return this.walletMap[accountAddress].clientKeySharesBackupInfo;
|
|
2201
2323
|
}
|
|
2202
2324
|
// Get backup info from server
|
|
2203
|
-
const user = await this.apiClient.getUser();
|
|
2325
|
+
const user = await this.apiClient.getUser(dynamicRequestId);
|
|
2204
2326
|
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
2205
2327
|
return getClientKeyShareBackupInfo({
|
|
2206
2328
|
walletProperties: wallet == null ? void 0 : wallet.walletProperties
|
|
2207
2329
|
});
|
|
2208
2330
|
} catch (error) {
|
|
2209
2331
|
logError({
|
|
2210
|
-
message: '
|
|
2332
|
+
message: 'Error in getWalletClientKeyShareBackupInfo',
|
|
2211
2333
|
error: error,
|
|
2212
2334
|
context: {
|
|
2213
|
-
accountAddress
|
|
2335
|
+
accountAddress,
|
|
2336
|
+
dynamicRequestId
|
|
2214
2337
|
}
|
|
2215
2338
|
});
|
|
2216
2339
|
throw error;
|
|
2217
2340
|
}
|
|
2218
2341
|
}
|
|
2219
2342
|
async getWallet({ accountAddress, walletOperation = core.WalletOperation.NO_OPERATION, shareCount = undefined, password = undefined, signedSessionId }) {
|
|
2343
|
+
const dynamicRequestId = uuid.v4();
|
|
2220
2344
|
try {
|
|
2221
2345
|
var _user_verifiedCredentials;
|
|
2222
2346
|
const existingWalletCheck = await this.checkWalletFields({
|
|
@@ -2229,7 +2353,7 @@ class DynamicWalletClient {
|
|
|
2229
2353
|
return this.walletMap[accountAddress];
|
|
2230
2354
|
}
|
|
2231
2355
|
// Fetch and restore wallet from server
|
|
2232
|
-
const user = await this.apiClient.getUser();
|
|
2356
|
+
const user = await this.apiClient.getUser(dynamicRequestId);
|
|
2233
2357
|
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
2234
2358
|
this.logger.debug('[DynamicWaasWalletClient] Restoring wallet', wallet);
|
|
2235
2359
|
const walletProperties = wallet.walletProperties;
|
|
@@ -2275,21 +2399,23 @@ class DynamicWalletClient {
|
|
|
2275
2399
|
return this.walletMap[accountAddress];
|
|
2276
2400
|
} catch (error) {
|
|
2277
2401
|
logError({
|
|
2278
|
-
message: '
|
|
2402
|
+
message: 'Error in getWallet',
|
|
2279
2403
|
error: error,
|
|
2280
2404
|
context: {
|
|
2281
2405
|
accountAddress,
|
|
2282
2406
|
walletOperation,
|
|
2283
|
-
shareCount
|
|
2407
|
+
shareCount,
|
|
2408
|
+
dynamicRequestId
|
|
2284
2409
|
}
|
|
2285
2410
|
});
|
|
2286
2411
|
throw error;
|
|
2287
2412
|
}
|
|
2288
2413
|
}
|
|
2289
2414
|
async getWallets() {
|
|
2415
|
+
const dynamicRequestId = uuid.v4();
|
|
2290
2416
|
try {
|
|
2291
2417
|
var _user_verifiedCredentials;
|
|
2292
|
-
const user = await this.apiClient.getUser();
|
|
2418
|
+
const user = await this.apiClient.getUser(dynamicRequestId);
|
|
2293
2419
|
this.userId = user.id;
|
|
2294
2420
|
const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
|
|
2295
2421
|
const wallets = waasWallets.map((vc)=>{
|
|
@@ -2322,9 +2448,11 @@ class DynamicWalletClient {
|
|
|
2322
2448
|
return wallets;
|
|
2323
2449
|
} catch (error) {
|
|
2324
2450
|
logError({
|
|
2325
|
-
message: '
|
|
2451
|
+
message: 'Error in getWallets',
|
|
2326
2452
|
error: error,
|
|
2327
|
-
context: {
|
|
2453
|
+
context: {
|
|
2454
|
+
dynamicRequestId
|
|
2455
|
+
}
|
|
2328
2456
|
});
|
|
2329
2457
|
throw error;
|
|
2330
2458
|
}
|
|
@@ -2342,7 +2470,6 @@ class DynamicWalletClient {
|
|
|
2342
2470
|
sdkVersion }){
|
|
2343
2471
|
this.userId = undefined;
|
|
2344
2472
|
this.sessionId = undefined;
|
|
2345
|
-
this.delegatedAccessEndpoint = undefined;
|
|
2346
2473
|
this.initializePromise = null;
|
|
2347
2474
|
this.logger = logger;
|
|
2348
2475
|
this.walletMap = {} // todo: store in session storage
|
|
@@ -2455,10 +2582,7 @@ exports.ERROR_SIGN_MESSAGE = ERROR_SIGN_MESSAGE;
|
|
|
2455
2582
|
exports.ERROR_SIGN_TYPED_DATA = ERROR_SIGN_TYPED_DATA;
|
|
2456
2583
|
exports.ERROR_VERIFY_MESSAGE_SIGNATURE = ERROR_VERIFY_MESSAGE_SIGNATURE;
|
|
2457
2584
|
exports.ERROR_VERIFY_TRANSACTION_SIGNATURE = ERROR_VERIFY_TRANSACTION_SIGNATURE;
|
|
2458
|
-
exports.base64ToBytes = base64ToBytes;
|
|
2459
|
-
exports.bytesToBase64 = bytesToBase64;
|
|
2460
2585
|
exports.createBackupData = createBackupData;
|
|
2461
|
-
exports.ensureBase64Padding = ensureBase64Padding;
|
|
2462
2586
|
exports.formatEvmMessage = formatEvmMessage;
|
|
2463
2587
|
exports.formatMessage = formatMessage;
|
|
2464
2588
|
exports.getClientKeyShareBackupInfo = getClientKeyShareBackupInfo;
|
|
@@ -2470,7 +2594,6 @@ exports.isBrowser = isBrowser;
|
|
|
2470
2594
|
exports.isHexString = isHexString;
|
|
2471
2595
|
exports.mergeUniqueKeyShares = mergeUniqueKeyShares;
|
|
2472
2596
|
exports.retryPromise = retryPromise;
|
|
2473
|
-
exports.stringToBytes = stringToBytes;
|
|
2474
2597
|
exports.timeoutPromise = timeoutPromise;
|
|
2475
2598
|
Object.keys(core).forEach(function (k) {
|
|
2476
2599
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|