@dynamic-labs-wallet/node 0.0.0-preview.160.0 → 0.0.1-paolo-1
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 +775 -233
- package/index.esm.js +763 -195
- package/internal/core/bip340.cjs +1 -0
- package/internal/core/common.cjs +1 -0
- package/internal/core/ecdsa.cjs +1 -0
- package/internal/core/ed25519.cjs +1 -0
- package/internal/core/ed25519_exportable.cjs +1 -0
- package/internal/core/index.cjs +1 -0
- package/internal/core/sr25519.cjs +1 -0
- package/internal/core/types.cjs +1 -0
- package/internal/node/index.cjs +1 -0
- package/internal/node/index.d.ts +1 -1
- package/internal/node/native.cjs +1 -0
- package/internal/node/native.d.ts +1 -1
- package/package.json +12 -4
- package/src/backup/encryption.d.ts +42 -2
- package/src/backup/encryption.d.ts.map +1 -1
- package/src/backup/utils.d.ts +4 -0
- package/src/backup/utils.d.ts.map +1 -0
- package/src/client.d.ts +82 -30
- package/src/client.d.ts.map +1 -1
- package/src/core/createCore.d.ts +25 -0
- package/src/core/createCore.d.ts.map +1 -0
- package/src/core/types.d.ts +7 -0
- package/src/core/types.d.ts.map +1 -0
- package/src/delegatedClient/createDelegatedClient.d.ts +23 -0
- package/src/delegatedClient/createDelegatedClient.d.ts.map +1 -0
- package/src/delegatedClient/index.d.ts +7 -0
- package/src/delegatedClient/index.d.ts.map +1 -0
- package/src/delegatedClient/modules/decryptWebhookData.d.ts +18 -0
- package/src/delegatedClient/modules/decryptWebhookData.d.ts.map +1 -0
- package/src/delegatedClient/modules/revokeDelegation.d.ts +3 -0
- package/src/delegatedClient/modules/revokeDelegation.d.ts.map +1 -0
- package/src/delegatedClient/modules/sign.d.ts +31 -0
- package/src/delegatedClient/modules/sign.d.ts.map +1 -0
- package/src/index.d.ts +11 -5
- package/src/index.d.ts.map +1 -1
- package/src/mpc/index.d.ts +2 -2
- package/src/mpc/index.d.ts.map +1 -1
- package/src/mpc/mpc.d.ts +3 -4
- package/src/mpc/mpc.d.ts.map +1 -1
- package/src/mpc/types.d.ts +1 -2
- package/src/mpc/types.d.ts.map +1 -1
- package/src/types.d.ts +11 -2
- package/src/types.d.ts.map +1 -1
- package/src/utils.d.ts +5 -2
- package/src/utils.d.ts.map +1 -1
- package/internal/core/bip340.js +0 -1
- package/internal/core/common.js +0 -1
- package/internal/core/ecdsa.js +0 -1
- package/internal/core/ed25519.js +0 -1
- package/internal/core/ed25519_exportable.js +0 -1
- package/internal/core/index.js +0 -1
- package/internal/core/sr25519.js +0 -1
- package/internal/core/types.js +0 -1
- package/internal/node/index.js +0 -1
- package/internal/node/native.js +0 -1
- /package/internal/core/{native.js → native.cjs} +0 -0
package/index.esm.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
1
|
+
export * from '#internal/core';
|
|
2
|
+
import { SigningAlgorithm, MPC_RELAY_PROD_API_URL, getMPCChainConfig, BackupLocation, DynamicApiClient, getClientThreshold, MPC_CONFIG, getTSSConfig, serializeMessageForForwardMPC, getEnvironmentFromUrl, MPC_RELAY_URL_MAP, WalletOperation, getServerWalletReshareConfig } from '@dynamic-labs-wallet/core';
|
|
3
|
+
export { SOLANA_RPC_URL, ThresholdSignatureScheme, WalletOperation, getMPCChainConfig } from '@dynamic-labs-wallet/core';
|
|
4
|
+
import { BIP340, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, BIP340KeygenResult } from '#internal/node';
|
|
5
|
+
import { v4 } from 'uuid';
|
|
6
6
|
import crypto from 'crypto';
|
|
7
|
+
import { Logger } from '@dynamic-labs/logger';
|
|
8
|
+
import 'node:crypto';
|
|
7
9
|
|
|
10
|
+
// Removed duplicate exports - these are already exported from #internal/core
|
|
8
11
|
const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD_API_URL })=>{
|
|
9
12
|
switch(signingAlgorithm){
|
|
10
13
|
case SigningAlgorithm.ECDSA:
|
|
11
14
|
return new Ecdsa(baseRelayUrl);
|
|
12
15
|
case SigningAlgorithm.ED25519:
|
|
13
|
-
return new
|
|
16
|
+
return new ExportableEd25519(baseRelayUrl);
|
|
14
17
|
case SigningAlgorithm.BIP340:
|
|
15
18
|
return new BIP340(baseRelayUrl);
|
|
16
19
|
default:
|
|
@@ -65,7 +68,8 @@ const getExternalServerKeyShareBackupInfo = (params)=>{
|
|
|
65
68
|
[BackupLocation.GOOGLE_DRIVE]: [],
|
|
66
69
|
[BackupLocation.ICLOUD]: [],
|
|
67
70
|
[BackupLocation.USER]: [],
|
|
68
|
-
[BackupLocation.EXTERNAL]: []
|
|
71
|
+
[BackupLocation.EXTERNAL]: [],
|
|
72
|
+
[BackupLocation.DELEGATED]: []
|
|
69
73
|
};
|
|
70
74
|
if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
|
|
71
75
|
return {
|
|
@@ -75,7 +79,11 @@ const getExternalServerKeyShareBackupInfo = (params)=>{
|
|
|
75
79
|
}
|
|
76
80
|
params.walletProperties.keyShares.forEach((keyShare)=>{
|
|
77
81
|
if (backups[keyShare.backupLocation]) {
|
|
78
|
-
backups[keyShare.backupLocation].push(
|
|
82
|
+
backups[keyShare.backupLocation].push({
|
|
83
|
+
location: keyShare.backupLocation,
|
|
84
|
+
keyShareId: keyShare.id,
|
|
85
|
+
passwordEncrypted: keyShare.passwordEncrypted
|
|
86
|
+
});
|
|
79
87
|
}
|
|
80
88
|
});
|
|
81
89
|
const passwordEncrypted = Boolean((_params_walletProperties_keyShares_ = params.walletProperties.keyShares[0]) == null ? void 0 : _params_walletProperties_keyShares_.passwordEncrypted);
|
|
@@ -125,13 +133,64 @@ const getExternalServerKeyShareBackupInfo = (params)=>{
|
|
|
125
133
|
// TypeScript needs this even though it's unreachable
|
|
126
134
|
throw new Error('Unreachable code');
|
|
127
135
|
}
|
|
136
|
+
const formatEvmMessage = (message)=>{
|
|
137
|
+
if (typeof message === 'string' && message.startsWith('0x')) {
|
|
138
|
+
const serializedTxBytes = Uint8Array.from(Buffer.from(message.slice(2), 'hex'));
|
|
139
|
+
return MessageHash.keccak256(serializedTxBytes);
|
|
140
|
+
}
|
|
141
|
+
return MessageHash.keccak256(message);
|
|
142
|
+
};
|
|
143
|
+
const formatSolanaMessage = (message)=>{
|
|
144
|
+
if (typeof message === 'string') {
|
|
145
|
+
if (!isHexString(message)) {
|
|
146
|
+
return Buffer.from(message).toString('hex');
|
|
147
|
+
} else {
|
|
148
|
+
return new Uint8Array(Buffer.from(message, 'hex'));
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
return message;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const formatMessage = (chainName, message)=>{
|
|
155
|
+
switch(chainName){
|
|
156
|
+
case 'EVM':
|
|
157
|
+
return formatEvmMessage(message);
|
|
158
|
+
case 'SVM':
|
|
159
|
+
return formatSolanaMessage(message);
|
|
160
|
+
case 'SUI':
|
|
161
|
+
return message;
|
|
162
|
+
default:
|
|
163
|
+
throw new Error('Unsupported chain name');
|
|
164
|
+
}
|
|
165
|
+
};
|
|
128
166
|
|
|
167
|
+
const ENCRYPTION_VERSION_LEGACY = 'v1';
|
|
168
|
+
const ENCRYPTION_VERSION_CURRENT = 'v2';
|
|
129
169
|
const PBKDF2_ALGORITHM = 'PBKDF2';
|
|
130
|
-
const PBKDF2_ITERATIONS = 100000;
|
|
131
170
|
const PBKDF2_HASH_ALGORITHM = 'SHA-256';
|
|
132
171
|
const AES_GCM_ALGORITHM = 'AES-GCM';
|
|
133
172
|
const AES_GCM_LENGTH = 256;
|
|
134
|
-
const
|
|
173
|
+
const ENCRYPTION_VERSIONS = {
|
|
174
|
+
[ENCRYPTION_VERSION_LEGACY]: {
|
|
175
|
+
version: ENCRYPTION_VERSION_LEGACY,
|
|
176
|
+
algorithm: AES_GCM_ALGORITHM,
|
|
177
|
+
keyDerivation: PBKDF2_ALGORITHM,
|
|
178
|
+
iterations: 100000,
|
|
179
|
+
hashAlgorithm: PBKDF2_HASH_ALGORITHM,
|
|
180
|
+
algorithmLength: AES_GCM_LENGTH
|
|
181
|
+
},
|
|
182
|
+
[ENCRYPTION_VERSION_CURRENT]: {
|
|
183
|
+
version: ENCRYPTION_VERSION_CURRENT,
|
|
184
|
+
algorithm: AES_GCM_ALGORITHM,
|
|
185
|
+
keyDerivation: PBKDF2_ALGORITHM,
|
|
186
|
+
iterations: 1000000,
|
|
187
|
+
hashAlgorithm: PBKDF2_HASH_ALGORITHM,
|
|
188
|
+
algorithmLength: AES_GCM_LENGTH
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
ENCRYPTION_VERSIONS[ENCRYPTION_VERSION_CURRENT].iterations;
|
|
192
|
+
ENCRYPTION_VERSIONS[ENCRYPTION_VERSION_LEGACY].iterations;
|
|
193
|
+
const getKey = async ({ password, salt, encryptionConfig })=>{
|
|
135
194
|
const passwordBytes = stringToBytes(password);
|
|
136
195
|
const initialKey = await crypto.subtle.importKey('raw', passwordBytes, {
|
|
137
196
|
name: 'PBKDF2'
|
|
@@ -139,26 +198,34 @@ const getKey = async ({ password, salt })=>{
|
|
|
139
198
|
'deriveKey'
|
|
140
199
|
]);
|
|
141
200
|
return crypto.subtle.deriveKey({
|
|
142
|
-
name:
|
|
143
|
-
salt,
|
|
144
|
-
iterations:
|
|
145
|
-
hash:
|
|
201
|
+
name: encryptionConfig.keyDerivation,
|
|
202
|
+
salt: salt,
|
|
203
|
+
iterations: encryptionConfig.iterations,
|
|
204
|
+
hash: encryptionConfig.hashAlgorithm
|
|
146
205
|
}, initialKey, {
|
|
147
|
-
name:
|
|
148
|
-
length:
|
|
206
|
+
name: encryptionConfig.algorithm,
|
|
207
|
+
length: encryptionConfig.algorithmLength
|
|
149
208
|
}, false, [
|
|
150
209
|
'encrypt',
|
|
151
210
|
'decrypt'
|
|
152
211
|
]);
|
|
153
212
|
};
|
|
154
|
-
|
|
213
|
+
/**
|
|
214
|
+
* Encrypts data using the specified encryption version.
|
|
215
|
+
* Always uses the latest encryption configuration for new encryptions by default.
|
|
216
|
+
*/ const encryptData = async ({ data, password, version = ENCRYPTION_VERSION_CURRENT })=>{
|
|
217
|
+
const encryptionConfig = ENCRYPTION_VERSIONS[version];
|
|
218
|
+
if (!encryptionConfig) {
|
|
219
|
+
throw new Error(`Unsupported encryption version: ${version}`);
|
|
220
|
+
}
|
|
155
221
|
try {
|
|
156
222
|
// Generate a random salt and IV
|
|
157
223
|
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
158
224
|
const iv = crypto.getRandomValues(new Uint8Array(12)); // AES-GCM requires 12 bytes
|
|
159
225
|
const key = await getKey({
|
|
160
226
|
password,
|
|
161
|
-
salt
|
|
227
|
+
salt,
|
|
228
|
+
encryptionConfig
|
|
162
229
|
});
|
|
163
230
|
// Convert the input string to bytes
|
|
164
231
|
const dataBytes = new TextEncoder().encode(data);
|
|
@@ -171,25 +238,39 @@ const encryptData = async ({ data, password })=>{
|
|
|
171
238
|
return {
|
|
172
239
|
salt: bytesToBase64(salt),
|
|
173
240
|
iv: bytesToBase64(iv),
|
|
174
|
-
cipher: bytesToBase64(new Uint8Array(encryptedData))
|
|
241
|
+
cipher: bytesToBase64(new Uint8Array(encryptedData)),
|
|
242
|
+
version
|
|
175
243
|
};
|
|
176
|
-
} catch (
|
|
244
|
+
} catch (e) {
|
|
177
245
|
throw new Error('Error encrypting data');
|
|
178
246
|
}
|
|
179
247
|
};
|
|
180
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Decrypts data with version-based configuration.
|
|
250
|
+
* Uses the version field from the data to determine encryption parameters.
|
|
251
|
+
* Falls back to legacy version for backward compatibility if no version is specified.
|
|
252
|
+
*/ const decryptData = async ({ data, password })=>{
|
|
253
|
+
const { salt, iv, cipher, version } = data;
|
|
254
|
+
// Ensure proper base64 padding for all values
|
|
255
|
+
const paddedSalt = ensureBase64Padding(salt);
|
|
256
|
+
const paddedIv = ensureBase64Padding(iv);
|
|
257
|
+
const paddedCipher = ensureBase64Padding(cipher);
|
|
258
|
+
const saltBytes = base64ToBytes(paddedSalt);
|
|
259
|
+
const ivBytes = base64ToBytes(paddedIv);
|
|
260
|
+
const cipherBytes = base64ToBytes(paddedCipher);
|
|
261
|
+
let encryptionConfig;
|
|
262
|
+
// Use version-based configuration if available, otherwise fallback to legacy
|
|
263
|
+
if (version && ENCRYPTION_VERSIONS[version]) {
|
|
264
|
+
encryptionConfig = ENCRYPTION_VERSIONS[version];
|
|
265
|
+
} else {
|
|
266
|
+
// Fallback to legacy version for backward compatibility
|
|
267
|
+
encryptionConfig = ENCRYPTION_VERSIONS[ENCRYPTION_VERSION_LEGACY];
|
|
268
|
+
}
|
|
181
269
|
try {
|
|
182
|
-
const { salt, iv, cipher } = data;
|
|
183
|
-
// Ensure proper base64 padding for all values
|
|
184
|
-
const paddedSalt = ensureBase64Padding(salt);
|
|
185
|
-
const paddedIv = ensureBase64Padding(iv);
|
|
186
|
-
const paddedCipher = ensureBase64Padding(cipher);
|
|
187
|
-
const saltBytes = base64ToBytes(paddedSalt);
|
|
188
|
-
const ivBytes = base64ToBytes(paddedIv);
|
|
189
|
-
const cipherBytes = base64ToBytes(paddedCipher);
|
|
190
270
|
const key = await getKey({
|
|
191
271
|
password,
|
|
192
|
-
salt: saltBytes
|
|
272
|
+
salt: saltBytes,
|
|
273
|
+
encryptionConfig
|
|
193
274
|
});
|
|
194
275
|
const decryptedData = await crypto.subtle.decrypt({
|
|
195
276
|
name: AES_GCM_ALGORITHM,
|
|
@@ -197,13 +278,23 @@ const decryptData = async ({ data, password })=>{
|
|
|
197
278
|
}, key, cipherBytes);
|
|
198
279
|
return new TextDecoder().decode(decryptedData);
|
|
199
280
|
} catch (error) {
|
|
200
|
-
throw new Error('Decryption failed');
|
|
281
|
+
throw new Error('Decryption failed: ' + error);
|
|
201
282
|
}
|
|
202
283
|
};
|
|
203
284
|
|
|
204
285
|
const DEFAULT_LOG_LEVEL = 'INFO';
|
|
205
286
|
|
|
206
287
|
class DynamicWalletClient {
|
|
288
|
+
async initializeForwardMPCClient() {
|
|
289
|
+
try {
|
|
290
|
+
await this.apiClient.forwardMPCClient.ensureWsConnection();
|
|
291
|
+
} catch (error) {
|
|
292
|
+
this.logger.error('Error connecting to ForwardMPC enclave websocket. Environment: ' + this.environmentId, {
|
|
293
|
+
error,
|
|
294
|
+
environmentId: this.environmentId
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
207
298
|
ensureApiClientAuthenticated() {
|
|
208
299
|
if (!this.isApiClientAuthenticated) {
|
|
209
300
|
throw new Error('Client must be authenticated before making API calls. Call authenticateApiToken first.');
|
|
@@ -227,13 +318,15 @@ class DynamicWalletClient {
|
|
|
227
318
|
});
|
|
228
319
|
this.isApiClientAuthenticated = true;
|
|
229
320
|
}
|
|
230
|
-
async dynamicServerInitializeKeyGen({ chainName, externalServerKeygenIds, thresholdSignatureScheme, onError, onCeremonyComplete }) {
|
|
321
|
+
async dynamicServerInitializeKeyGen({ chainName, externalServerKeygenIds, thresholdSignatureScheme, dynamicRequestId, skipLock, onError, onCeremonyComplete }) {
|
|
231
322
|
this.ensureApiClientAuthenticated();
|
|
232
323
|
try {
|
|
233
324
|
const data = await this.apiClient.createWalletAccount({
|
|
234
325
|
chainName,
|
|
235
326
|
clientKeygenIds: externalServerKeygenIds,
|
|
236
327
|
thresholdSignatureScheme,
|
|
328
|
+
dynamicRequestId,
|
|
329
|
+
skipLock,
|
|
237
330
|
onError,
|
|
238
331
|
onCeremonyComplete
|
|
239
332
|
});
|
|
@@ -267,8 +360,8 @@ class DynamicWalletClient {
|
|
|
267
360
|
let publicKey;
|
|
268
361
|
if (mpcSigner instanceof Ecdsa) {
|
|
269
362
|
publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
|
|
270
|
-
} else if (mpcSigner instanceof
|
|
271
|
-
publicKey = await mpcSigner.
|
|
363
|
+
} else if (mpcSigner instanceof ExportableEd25519) {
|
|
364
|
+
publicKey = await mpcSigner.getPubkey(keyShare);
|
|
272
365
|
}
|
|
273
366
|
return publicKey;
|
|
274
367
|
} catch (error) {
|
|
@@ -294,7 +387,13 @@ class DynamicWalletClient {
|
|
|
294
387
|
...dynamicServerKeygenIds,
|
|
295
388
|
...otherExternalServerKeygenIds
|
|
296
389
|
];
|
|
297
|
-
|
|
390
|
+
if (!(mpcSigner instanceof ExportableEd25519)) {
|
|
391
|
+
return mpcSigner.keygen(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
|
|
392
|
+
} else {
|
|
393
|
+
// One party joins the keygen room using acting as the sampler: (wallet-service)
|
|
394
|
+
// The remaining parties join the key sampling ceremony using: (browser)
|
|
395
|
+
return mpcSigner.receiveKey(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
|
|
396
|
+
}
|
|
298
397
|
}));
|
|
299
398
|
// only need one client keygen result to derive the public key
|
|
300
399
|
const [serverKeygenResult] = serverKeygenResults;
|
|
@@ -314,7 +413,8 @@ class DynamicWalletClient {
|
|
|
314
413
|
throw new Error('Error deriving public key in externalServerKeyGen');
|
|
315
414
|
}
|
|
316
415
|
}
|
|
317
|
-
async keyGen({ chainName, thresholdSignatureScheme, onError, onCeremonyComplete }) {
|
|
416
|
+
async keyGen({ chainName, thresholdSignatureScheme, skipLock, onError, onCeremonyComplete }) {
|
|
417
|
+
const dynamicRequestId = v4();
|
|
318
418
|
try {
|
|
319
419
|
const externalServerInitKeygenResults = await this.externalServerInitializeKeyGen({
|
|
320
420
|
chainName,
|
|
@@ -324,7 +424,9 @@ class DynamicWalletClient {
|
|
|
324
424
|
const { roomId, serverKeygenIds: dynamicServerKeygenIds } = await this.dynamicServerInitializeKeyGen({
|
|
325
425
|
chainName,
|
|
326
426
|
externalServerKeygenIds,
|
|
427
|
+
dynamicRequestId,
|
|
327
428
|
thresholdSignatureScheme,
|
|
429
|
+
skipLock,
|
|
328
430
|
onCeremonyComplete
|
|
329
431
|
});
|
|
330
432
|
const { rawPublicKey, externalServerKeyGenResults } = await this.externalServerKeyGen({
|
|
@@ -345,6 +447,7 @@ class DynamicWalletClient {
|
|
|
345
447
|
}
|
|
346
448
|
async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, onError, onCeremonyComplete }) {
|
|
347
449
|
this.ensureApiClientAuthenticated();
|
|
450
|
+
const dynamicRequestId = v4();
|
|
348
451
|
const mpcSigner = getMPCSigner({
|
|
349
452
|
chainName,
|
|
350
453
|
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
@@ -358,6 +461,7 @@ class DynamicWalletClient {
|
|
|
358
461
|
chainName,
|
|
359
462
|
clientKeygenIds: externalServerKeygenIds,
|
|
360
463
|
thresholdSignatureScheme,
|
|
464
|
+
dynamicRequestId,
|
|
361
465
|
onError,
|
|
362
466
|
onCeremonyComplete
|
|
363
467
|
});
|
|
@@ -390,7 +494,8 @@ class DynamicWalletClient {
|
|
|
390
494
|
externalServerKeyShares: externalServerKeygenResults
|
|
391
495
|
};
|
|
392
496
|
}
|
|
393
|
-
async dynamicServerSign({ walletId, message }) {
|
|
497
|
+
async dynamicServerSign({ walletId, message, isFormatted, context, onError }) {
|
|
498
|
+
const dynamicRequestId = v4();
|
|
394
499
|
this.ensureApiClientAuthenticated();
|
|
395
500
|
// Create the room and sign the message
|
|
396
501
|
if (typeof message !== 'string') {
|
|
@@ -398,35 +503,22 @@ class DynamicWalletClient {
|
|
|
398
503
|
}
|
|
399
504
|
const data = await this.apiClient.signMessage({
|
|
400
505
|
walletId,
|
|
401
|
-
message
|
|
506
|
+
message,
|
|
507
|
+
isFormatted,
|
|
508
|
+
dynamicRequestId,
|
|
509
|
+
context: context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined,
|
|
510
|
+
onError,
|
|
511
|
+
forwardMPCClientEnabled: this.forwardMPCEnabled
|
|
402
512
|
});
|
|
403
513
|
return data;
|
|
404
514
|
}
|
|
405
|
-
async externalServerSign({ chainName, message, roomId, keyShare, derivationPath }) {
|
|
515
|
+
async externalServerSign({ chainName, message, roomId, keyShare, derivationPath, isFormatted }) {
|
|
406
516
|
try {
|
|
407
517
|
const mpcSigner = getMPCSigner({
|
|
408
518
|
chainName,
|
|
409
519
|
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
410
520
|
});
|
|
411
|
-
|
|
412
|
-
//note: Ecdsa can also be used by bitcoin, but only keccak256 is used by ethereum
|
|
413
|
-
if (mpcSigner instanceof Ecdsa) {
|
|
414
|
-
formattedMessage = MessageHash.keccak256(message);
|
|
415
|
-
} else if (mpcSigner instanceof Ed25519) {
|
|
416
|
-
if (typeof message === 'string') {
|
|
417
|
-
if (!isHexString(message)) {
|
|
418
|
-
formattedMessage = Buffer.from(message).toString('hex');
|
|
419
|
-
} else {
|
|
420
|
-
formattedMessage = Buffer.from(message, 'hex');
|
|
421
|
-
}
|
|
422
|
-
} else {
|
|
423
|
-
formattedMessage = message;
|
|
424
|
-
}
|
|
425
|
-
} else if (mpcSigner instanceof BIP340 && typeof message === 'string') {
|
|
426
|
-
formattedMessage = new TextEncoder().encode(message);
|
|
427
|
-
} else {
|
|
428
|
-
throw new Error('Unsupported signer type');
|
|
429
|
-
}
|
|
521
|
+
const formattedMessage = isFormatted ? new MessageHash(message) : formatMessage(chainName, message);
|
|
430
522
|
const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath);
|
|
431
523
|
return signature;
|
|
432
524
|
} catch (error) {
|
|
@@ -434,35 +526,104 @@ class DynamicWalletClient {
|
|
|
434
526
|
throw error;
|
|
435
527
|
}
|
|
436
528
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
529
|
+
async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted }) {
|
|
530
|
+
try {
|
|
531
|
+
if (!this.apiClient.forwardMPCClient.connected) {
|
|
532
|
+
await this.initializeForwardMPCClient();
|
|
533
|
+
}
|
|
534
|
+
const messageForForwardMPC = serializeMessageForForwardMPC({
|
|
535
|
+
message,
|
|
536
|
+
isFormatted,
|
|
537
|
+
chainName
|
|
538
|
+
});
|
|
539
|
+
this.logger.info('Forward MPC enabled, signing message with forward MPC');
|
|
540
|
+
const environment = getEnvironmentFromUrl(this.baseApiUrl);
|
|
541
|
+
const defaultRelayUrl = MPC_RELAY_URL_MAP[environment];
|
|
542
|
+
const signature = await this.apiClient.forwardMPCClient.signMessage({
|
|
543
|
+
keyshare: keyShare,
|
|
544
|
+
message: chainName === 'SVM' ? formattedMessage : messageForForwardMPC,
|
|
545
|
+
relayDomain: this.baseMPCRelayApiUrl || defaultRelayUrl,
|
|
546
|
+
signingAlgo: chainName === 'EVM' ? 'ECDSA' : 'ED25519',
|
|
547
|
+
hashAlgo: chainName === 'EVM' && !isFormatted ? 'keccak256' : undefined,
|
|
548
|
+
derivationPath: derivationPath,
|
|
549
|
+
roomUuid: roomId
|
|
550
|
+
});
|
|
551
|
+
const signatureBytes = signature.data.signature;
|
|
552
|
+
if (!(signatureBytes instanceof Uint8Array)) {
|
|
553
|
+
throw new TypeError(`Invalid signature format: expected Uint8Array, got ${typeof signatureBytes}`);
|
|
554
|
+
}
|
|
555
|
+
// Convert to EcdsaSignature
|
|
556
|
+
if (chainName === 'EVM') {
|
|
557
|
+
const ecdsaSignature = EcdsaSignature.fromBuffer(signatureBytes);
|
|
558
|
+
return ecdsaSignature;
|
|
559
|
+
} else {
|
|
560
|
+
return signatureBytes;
|
|
561
|
+
}
|
|
562
|
+
} catch (error) {
|
|
563
|
+
this.logger.error('Error signing message with forward MPC client', {
|
|
564
|
+
error,
|
|
565
|
+
environmentId: this.environmentId,
|
|
566
|
+
dynamicRequestId
|
|
567
|
+
});
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async sign({ accountAddress, externalServerKeyShares, message, chainName, password = undefined, isFormatted = false, context, onError }) {
|
|
572
|
+
try {
|
|
573
|
+
await this.verifyPassword({
|
|
574
|
+
accountAddress,
|
|
575
|
+
password,
|
|
576
|
+
walletOperation: WalletOperation.SIGN_MESSAGE
|
|
577
|
+
});
|
|
578
|
+
const wallet = await this.getWallet({
|
|
579
|
+
accountAddress,
|
|
580
|
+
walletOperation: WalletOperation.SIGN_MESSAGE,
|
|
581
|
+
password
|
|
582
|
+
});
|
|
583
|
+
externalServerKeyShares = externalServerKeyShares != null ? externalServerKeyShares : wallet.externalServerKeyShares;
|
|
584
|
+
if (!externalServerKeyShares) {
|
|
585
|
+
throw new Error('External server key shares are required to sign a message');
|
|
586
|
+
}
|
|
587
|
+
// Perform the dynamic server sign
|
|
588
|
+
const data = await this.dynamicServerSign({
|
|
589
|
+
walletId: wallet.walletId,
|
|
590
|
+
message,
|
|
591
|
+
isFormatted,
|
|
592
|
+
context,
|
|
593
|
+
onError
|
|
594
|
+
});
|
|
595
|
+
const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
|
|
596
|
+
// Perform the external server sign and return the signature
|
|
597
|
+
if (this.forwardMPCEnabled) {
|
|
598
|
+
const formattedMessage = isFormatted ? new MessageHash(message) : formatMessage(chainName, message);
|
|
599
|
+
const signature = await this.forwardMPCClientSign({
|
|
600
|
+
chainName,
|
|
601
|
+
message,
|
|
602
|
+
roomId: data.roomId,
|
|
603
|
+
keyShare: externalServerKeyShares[0],
|
|
604
|
+
derivationPath,
|
|
605
|
+
formattedMessage,
|
|
606
|
+
dynamicRequestId: v4(),
|
|
607
|
+
isFormatted
|
|
608
|
+
});
|
|
609
|
+
return signature;
|
|
610
|
+
} else {
|
|
611
|
+
const signature = await this.externalServerSign({
|
|
612
|
+
chainName,
|
|
613
|
+
message,
|
|
614
|
+
roomId: data.roomId,
|
|
615
|
+
keyShare: externalServerKeyShares[0],
|
|
616
|
+
derivationPath,
|
|
617
|
+
isFormatted
|
|
618
|
+
});
|
|
619
|
+
return signature;
|
|
620
|
+
}
|
|
621
|
+
} catch (error) {
|
|
622
|
+
this.logger.error('Error in sign', error);
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
464
625
|
}
|
|
465
|
-
async refreshWalletAccountShares({ accountAddress, chainName, password = undefined }) {
|
|
626
|
+
async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, externalServerKeyShares, backUpToClientShareService = false }) {
|
|
466
627
|
this.ensureApiClientAuthenticated();
|
|
467
628
|
await this.verifyPassword({
|
|
468
629
|
accountAddress,
|
|
@@ -483,14 +644,19 @@ class DynamicWalletClient {
|
|
|
483
644
|
walletId: wallet.walletId
|
|
484
645
|
});
|
|
485
646
|
const roomId = data.roomId;
|
|
486
|
-
|
|
647
|
+
externalServerKeyShares = externalServerKeyShares != null ? externalServerKeyShares : wallet.externalServerKeyShares;
|
|
648
|
+
if (!externalServerKeyShares) {
|
|
649
|
+
throw new Error('External server key shares are required to refresh');
|
|
650
|
+
}
|
|
651
|
+
const refreshResults = await Promise.all(externalServerKeyShares.map((serverKeyShare)=>mpcSigner.refresh(roomId, serverKeyShare)));
|
|
487
652
|
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
488
653
|
externalServerKeyShares: refreshResults,
|
|
489
654
|
externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo()
|
|
490
655
|
});
|
|
491
656
|
await this.storeEncryptedBackupByWallet({
|
|
492
657
|
accountAddress,
|
|
493
|
-
password: password != null ? password : this.environmentId
|
|
658
|
+
password: password != null ? password : this.environmentId,
|
|
659
|
+
backUpToClientShareService
|
|
494
660
|
});
|
|
495
661
|
return refreshResults;
|
|
496
662
|
}
|
|
@@ -543,7 +709,7 @@ class DynamicWalletClient {
|
|
|
543
709
|
existingExternalServerKeyShares
|
|
544
710
|
};
|
|
545
711
|
}
|
|
546
|
-
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined }) {
|
|
712
|
+
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, externalServerKeyShares, backUpToClientShareService = false }) {
|
|
547
713
|
this.ensureApiClientAuthenticated();
|
|
548
714
|
await this.verifyPassword({
|
|
549
715
|
accountAddress,
|
|
@@ -560,9 +726,15 @@ class DynamicWalletClient {
|
|
|
560
726
|
shareCount: existingExternalServerShareCount,
|
|
561
727
|
password
|
|
562
728
|
});
|
|
729
|
+
externalServerKeyShares = externalServerKeyShares != null ? externalServerKeyShares : wallet.externalServerKeyShares;
|
|
730
|
+
if (!externalServerKeyShares) {
|
|
731
|
+
throw new Error('External server key shares are required to reshare');
|
|
732
|
+
}
|
|
563
733
|
const { newExternalServerInitKeygenResults, newExternalServerKeygenIds, existingExternalServerKeygenIds, existingExternalServerKeyShares } = await this.reshareStrategy({
|
|
564
734
|
chainName,
|
|
565
|
-
wallet,
|
|
735
|
+
wallet: _extends({}, wallet, {
|
|
736
|
+
externalServerKeyShares
|
|
737
|
+
}),
|
|
566
738
|
oldThresholdSignatureScheme,
|
|
567
739
|
newThresholdSignatureScheme
|
|
568
740
|
});
|
|
@@ -600,11 +772,12 @@ class DynamicWalletClient {
|
|
|
600
772
|
});
|
|
601
773
|
await this.storeEncryptedBackupByWallet({
|
|
602
774
|
accountAddress,
|
|
603
|
-
password
|
|
775
|
+
password,
|
|
776
|
+
backUpToClientShareService
|
|
604
777
|
});
|
|
605
778
|
return reshareResults;
|
|
606
779
|
}
|
|
607
|
-
async exportKey({ accountAddress, chainName, password = undefined }) {
|
|
780
|
+
async exportKey({ accountAddress, chainName, password = undefined, externalServerKeyShares }) {
|
|
608
781
|
this.ensureApiClientAuthenticated();
|
|
609
782
|
await this.verifyPassword({
|
|
610
783
|
accountAddress,
|
|
@@ -616,19 +789,23 @@ class DynamicWalletClient {
|
|
|
616
789
|
password,
|
|
617
790
|
walletOperation: WalletOperation.EXPORT_PRIVATE_KEY
|
|
618
791
|
});
|
|
792
|
+
externalServerKeyShares = externalServerKeyShares != null ? externalServerKeyShares : wallet.externalServerKeyShares;
|
|
793
|
+
if (!externalServerKeyShares) {
|
|
794
|
+
throw new Error('External server key shares are required to export a private key');
|
|
795
|
+
}
|
|
619
796
|
const mpcSigner = getMPCSigner({
|
|
620
797
|
chainName,
|
|
621
798
|
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
622
799
|
});
|
|
623
800
|
const exportId = await this.getExportId({
|
|
624
801
|
chainName,
|
|
625
|
-
serverKeyShare:
|
|
802
|
+
serverKeyShare: externalServerKeyShares[0]
|
|
626
803
|
});
|
|
627
804
|
const data = await this.apiClient.exportKey({
|
|
628
805
|
walletId: wallet.walletId,
|
|
629
806
|
exportId
|
|
630
807
|
});
|
|
631
|
-
const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId,
|
|
808
|
+
const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId, externalServerKeyShares[0], exportId);
|
|
632
809
|
if (!keyExportRaw) {
|
|
633
810
|
throw new Error('Error exporting private key');
|
|
634
811
|
}
|
|
@@ -636,7 +813,7 @@ class DynamicWalletClient {
|
|
|
636
813
|
let derivedPrivateKey;
|
|
637
814
|
if (mpcSigner instanceof Ecdsa) {
|
|
638
815
|
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
|
|
639
|
-
} else if (mpcSigner instanceof
|
|
816
|
+
} else if (mpcSigner instanceof ExportableEd25519) {
|
|
640
817
|
derivedPrivateKey = keyExportRaw;
|
|
641
818
|
} else if (mpcSigner instanceof BIP340) {
|
|
642
819
|
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
|
|
@@ -655,7 +832,7 @@ class DynamicWalletClient {
|
|
|
655
832
|
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
656
833
|
});
|
|
657
834
|
const walletKeyShares = keyShares.map((keyShare)=>{
|
|
658
|
-
return mpcSigner instanceof Ecdsa ? new EcdsaKeygenResult(keyShare.pubkey, keyShare.secretShare) : mpcSigner instanceof
|
|
835
|
+
return mpcSigner instanceof Ecdsa ? new EcdsaKeygenResult(keyShare.pubkey, keyShare.secretShare) : mpcSigner instanceof ExportableEd25519 ? new ExportableEd25519KeygenResult(keyShare.pubkey, keyShare.secretShare) : new BIP340KeygenResult(keyShare.pubkey, keyShare.secretShare);
|
|
659
836
|
});
|
|
660
837
|
const keyExportRaw = await mpcSigner.offlineExportFullPrivateKey(walletKeyShares);
|
|
661
838
|
if (!keyExportRaw) {
|
|
@@ -666,7 +843,7 @@ class DynamicWalletClient {
|
|
|
666
843
|
let derivedPrivateKey;
|
|
667
844
|
if (mpcSigner instanceof Ecdsa) {
|
|
668
845
|
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
|
|
669
|
-
} else if (mpcSigner instanceof
|
|
846
|
+
} else if (mpcSigner instanceof ExportableEd25519) {
|
|
670
847
|
derivedPrivateKey = keyExportRaw;
|
|
671
848
|
} else if (mpcSigner instanceof BIP340) {
|
|
672
849
|
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
|
|
@@ -700,15 +877,17 @@ class DynamicWalletClient {
|
|
|
700
877
|
throw new Error('Ceremony completion timeout');
|
|
701
878
|
}
|
|
702
879
|
}
|
|
703
|
-
async storeEncryptedBackupByWallet({ accountAddress, externalServerKeyShares = undefined, password = undefined }) {
|
|
880
|
+
async storeEncryptedBackupByWallet({ accountAddress, externalServerKeyShares = undefined, password = undefined, backUpToClientShareService }) {
|
|
704
881
|
this.ensureApiClientAuthenticated();
|
|
705
882
|
//add retry logic for ceremony completion to prevent race condition
|
|
706
883
|
await this.ensureCeremonyCompletionBeforeBackup({
|
|
707
884
|
accountAddress
|
|
708
885
|
});
|
|
886
|
+
const dynamicRequestId = v4();
|
|
709
887
|
try {
|
|
710
888
|
const keySharesToBackup = externalServerKeyShares != null ? externalServerKeyShares : await this.getExternalServerKeyShares({
|
|
711
|
-
accountAddress
|
|
889
|
+
accountAddress,
|
|
890
|
+
password
|
|
712
891
|
});
|
|
713
892
|
if (!keySharesToBackup || keySharesToBackup.length === 0) {
|
|
714
893
|
throw new Error(`Key shares not found for accountAddress: ${accountAddress}`);
|
|
@@ -720,51 +899,92 @@ class DynamicWalletClient {
|
|
|
720
899
|
if (!this.walletMap[accountAddress] || !this.walletMap[accountAddress].walletId) {
|
|
721
900
|
throw new Error(`WalletId not found for accountAddress: ${accountAddress}`);
|
|
722
901
|
}
|
|
723
|
-
const
|
|
902
|
+
const passwordEncryptedFlag = Boolean(password) && password !== this.environmentId;
|
|
903
|
+
const locations = [];
|
|
904
|
+
if (backUpToClientShareService) {
|
|
905
|
+
const data = await this.apiClient.storeEncryptedBackupByWallet({
|
|
906
|
+
walletId: this.walletMap[accountAddress].walletId,
|
|
907
|
+
encryptedKeyShares: encryptedKeyShares,
|
|
908
|
+
passwordEncrypted: passwordEncryptedFlag,
|
|
909
|
+
encryptionVersion: ENCRYPTION_VERSION_CURRENT,
|
|
910
|
+
requiresSignedSessionId: false,
|
|
911
|
+
dynamicRequestId
|
|
912
|
+
});
|
|
913
|
+
locations.push({
|
|
914
|
+
location: BackupLocation.DYNAMIC,
|
|
915
|
+
externalKeyShareId: data.keyShareIds[0],
|
|
916
|
+
passwordEncrypted: passwordEncryptedFlag
|
|
917
|
+
});
|
|
918
|
+
} else {
|
|
919
|
+
locations.push({
|
|
920
|
+
location: BackupLocation.EXTERNAL
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
const backupData = await this.apiClient.markKeySharesAsBackedUp({
|
|
724
924
|
walletId: this.walletMap[accountAddress].walletId,
|
|
725
|
-
|
|
726
|
-
|
|
925
|
+
locations,
|
|
926
|
+
dynamicRequestId
|
|
927
|
+
});
|
|
928
|
+
const updatedBackupInfo = getExternalServerKeyShareBackupInfo({
|
|
929
|
+
walletProperties: {
|
|
930
|
+
derivationPath: this.walletMap[accountAddress].derivationPath,
|
|
931
|
+
keyShares: backupData.locationsWithKeyShares.map((ks)=>({
|
|
932
|
+
id: ks.keyShareId,
|
|
933
|
+
backupLocation: ks.location,
|
|
934
|
+
externalKeyShareId: ks.externalKeyShareId,
|
|
935
|
+
passwordEncrypted: passwordEncryptedFlag
|
|
936
|
+
})),
|
|
937
|
+
thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
|
|
938
|
+
}
|
|
727
939
|
});
|
|
728
940
|
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
729
|
-
externalServerKeySharesBackupInfo:
|
|
730
|
-
walletProperties: {
|
|
731
|
-
derivationPath: this.walletMap[accountAddress].derivationPath,
|
|
732
|
-
keyShares: data.keyShares,
|
|
733
|
-
thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
|
|
734
|
-
}
|
|
735
|
-
})
|
|
941
|
+
externalServerKeySharesBackupInfo: updatedBackupInfo
|
|
736
942
|
});
|
|
737
|
-
return
|
|
943
|
+
return encryptedKeyShares;
|
|
738
944
|
} catch (error) {
|
|
739
|
-
this.logger.error('Error in storeEncryptedBackupByWallet:',
|
|
945
|
+
this.logger.error('Error in storeEncryptedBackupByWallet:', {
|
|
946
|
+
accountAddress,
|
|
947
|
+
error,
|
|
948
|
+
dynamicRequestId
|
|
949
|
+
});
|
|
740
950
|
throw error;
|
|
741
951
|
}
|
|
742
952
|
}
|
|
743
|
-
async storeEncryptedBackupByWalletWithRetry({ accountAddress, externalServerKeyShares, password }) {
|
|
953
|
+
async storeEncryptedBackupByWalletWithRetry({ accountAddress, externalServerKeyShares, password, backUpToClientShareService }) {
|
|
744
954
|
await retryPromise(()=>this.storeEncryptedBackupByWallet({
|
|
745
955
|
accountAddress,
|
|
746
956
|
externalServerKeyShares,
|
|
747
|
-
password
|
|
957
|
+
password,
|
|
958
|
+
backUpToClientShareService
|
|
748
959
|
}), {
|
|
749
960
|
operationName: 'store encrypted backup',
|
|
750
961
|
logContext: {
|
|
751
|
-
walletAddress: accountAddress
|
|
752
|
-
keyShares: externalServerKeyShares == null ? void 0 : externalServerKeyShares.map((keyShare)=>this.encryptKeyShare({
|
|
753
|
-
keyShare,
|
|
754
|
-
password
|
|
755
|
-
}))
|
|
962
|
+
walletAddress: accountAddress
|
|
756
963
|
}
|
|
757
964
|
});
|
|
758
965
|
}
|
|
759
966
|
async getExternalServerKeyShares({ accountAddress, password }) {
|
|
967
|
+
var _wallet_externalServerKeySharesBackupInfo_backups, _wallet_externalServerKeySharesBackupInfo;
|
|
760
968
|
const wallet = await this.getWallet({
|
|
761
969
|
accountAddress,
|
|
762
970
|
password,
|
|
763
971
|
walletOperation: WalletOperation.REACH_THRESHOLD
|
|
764
972
|
});
|
|
765
|
-
|
|
973
|
+
// Check if the wallet has a dynamic backup and derive password encryption status from the first dynamic share
|
|
974
|
+
const dynamicBackups = (wallet == null ? void 0 : (_wallet_externalServerKeySharesBackupInfo = wallet.externalServerKeySharesBackupInfo) == null ? void 0 : (_wallet_externalServerKeySharesBackupInfo_backups = _wallet_externalServerKeySharesBackupInfo.backups) == null ? void 0 : _wallet_externalServerKeySharesBackupInfo_backups.dynamic) || [];
|
|
975
|
+
const passwordEncrypted = dynamicBackups.length > 0 ? Boolean(dynamicBackups[0].passwordEncrypted) : false;
|
|
976
|
+
if (passwordEncrypted && !password) {
|
|
977
|
+
throw new Error('Password is required for decryption but not provided. This backup was encrypted with a password.');
|
|
978
|
+
}
|
|
979
|
+
// recover the shares
|
|
980
|
+
const recoveredShares = await this.recoverEncryptedBackupByWallet({
|
|
981
|
+
accountAddress,
|
|
982
|
+
password,
|
|
983
|
+
walletOperation: WalletOperation.REACH_THRESHOLD
|
|
984
|
+
});
|
|
985
|
+
return recoveredShares;
|
|
766
986
|
}
|
|
767
|
-
async updatePassword({ accountAddress, existingPassword, newPassword }) {
|
|
987
|
+
async updatePassword({ accountAddress, existingPassword, newPassword, backUpToClientShareService }) {
|
|
768
988
|
await this.getWallet({
|
|
769
989
|
accountAddress,
|
|
770
990
|
password: existingPassword,
|
|
@@ -772,7 +992,8 @@ class DynamicWalletClient {
|
|
|
772
992
|
});
|
|
773
993
|
await this.storeEncryptedBackupByWallet({
|
|
774
994
|
accountAddress,
|
|
775
|
-
password: newPassword
|
|
995
|
+
password: newPassword,
|
|
996
|
+
backUpToClientShareService
|
|
776
997
|
});
|
|
777
998
|
}
|
|
778
999
|
async decryptKeyShare({ keyShare, password }) {
|
|
@@ -803,17 +1024,57 @@ class DynamicWalletClient {
|
|
|
803
1024
|
if (shareCount !== undefined) {
|
|
804
1025
|
requiredShareCount = shareCount;
|
|
805
1026
|
}
|
|
806
|
-
const dynamicShares = backups[BackupLocation.DYNAMIC].slice(0, requiredShareCount);
|
|
1027
|
+
const dynamicShares = (backups[BackupLocation.DYNAMIC] || []).slice(0, requiredShareCount);
|
|
1028
|
+
const externalShares = (backups[BackupLocation.EXTERNAL] || []).slice(0, requiredShareCount);
|
|
1029
|
+
// If we have DYNAMIC shares, use those; otherwise use EXTERNAL shares
|
|
1030
|
+
const sharesToUse = dynamicShares.length > 0 ? dynamicShares : externalShares;
|
|
1031
|
+
const backupLocation = dynamicShares.length > 0 ? BackupLocation.DYNAMIC : BackupLocation.EXTERNAL;
|
|
807
1032
|
return {
|
|
808
1033
|
shares: {
|
|
809
|
-
[
|
|
1034
|
+
[backupLocation]: sharesToUse.map((ks)=>{
|
|
1035
|
+
var _ks_externalKeyShareId, _ref;
|
|
1036
|
+
return (_ref = (_ks_externalKeyShareId = ks.externalKeyShareId) != null ? _ks_externalKeyShareId : ks.keyShareId) != null ? _ref : '';
|
|
1037
|
+
})
|
|
810
1038
|
},
|
|
811
1039
|
requiredShareCount
|
|
812
1040
|
};
|
|
813
1041
|
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Attempts to recover key shares from backup if they are not provided.
|
|
1044
|
+
* The recovered shares will be stored in the wallet map for use in signing operations.
|
|
1045
|
+
* @param accountAddress - The account address to recover shares for
|
|
1046
|
+
* @param password - The password to decrypt the shares
|
|
1047
|
+
* @param walletOperation - The wallet operation being performed
|
|
1048
|
+
* @param externalServerKeyShares - The provided key shares (if any)
|
|
1049
|
+
* @param errorMessage - The error message to throw if recovery fails
|
|
1050
|
+
* @throws Error if recovery is needed but fails
|
|
1051
|
+
*/ async ensureKeySharesRecovered({ accountAddress, password, walletOperation, externalServerKeyShares, errorMessage }) {
|
|
1052
|
+
if (!externalServerKeyShares || externalServerKeyShares.length === 0) {
|
|
1053
|
+
// attempt to recover dynamic keyshares if no key shares are provided
|
|
1054
|
+
// the shares will be used for signing in the node SDK if successful
|
|
1055
|
+
const recoveredShares = await this.recoverEncryptedBackupByWallet({
|
|
1056
|
+
accountAddress,
|
|
1057
|
+
password,
|
|
1058
|
+
walletOperation,
|
|
1059
|
+
storeRecoveredShares: true
|
|
1060
|
+
});
|
|
1061
|
+
// If recovery returned empty and no shares were provided, throw error
|
|
1062
|
+
if (!recoveredShares || recoveredShares.length === 0) {
|
|
1063
|
+
throw new Error(errorMessage);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
814
1067
|
async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, shareCount = undefined, storeRecoveredShares = true }) {
|
|
1068
|
+
var _wallet_externalServerKeySharesBackupInfo;
|
|
815
1069
|
this.ensureApiClientAuthenticated();
|
|
816
|
-
|
|
1070
|
+
let wallet = this.walletMap[accountAddress];
|
|
1071
|
+
if (!wallet) {
|
|
1072
|
+
const fetchedWallet = await this.getWalletByAddress(accountAddress);
|
|
1073
|
+
if (!fetchedWallet) {
|
|
1074
|
+
throw new Error(`Wallet not found for address 1: ${accountAddress}`);
|
|
1075
|
+
}
|
|
1076
|
+
wallet = fetchedWallet;
|
|
1077
|
+
}
|
|
817
1078
|
this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, wallet);
|
|
818
1079
|
const { shares } = this.recoverStrategy({
|
|
819
1080
|
externalServerKeySharesBackupInfo: wallet.externalServerKeySharesBackupInfo,
|
|
@@ -821,12 +1082,30 @@ class DynamicWalletClient {
|
|
|
821
1082
|
walletOperation,
|
|
822
1083
|
shareCount
|
|
823
1084
|
});
|
|
824
|
-
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1085
|
+
// Get the key share IDs from whichever backup location has shares
|
|
1086
|
+
const dynamicKeyShareIds = shares[BackupLocation.DYNAMIC] || [];
|
|
1087
|
+
const externalKeyShareIds = shares[BackupLocation.EXTERNAL] || [];
|
|
1088
|
+
// Only attempt recovery if we have DYNAMIC shares (the API doesn't support EXTERNAL shares)
|
|
1089
|
+
let data;
|
|
1090
|
+
if (dynamicKeyShareIds.length > 0) {
|
|
1091
|
+
data = await this.apiClient.recoverEncryptedBackupByWallet({
|
|
1092
|
+
walletId: wallet.walletId,
|
|
1093
|
+
keyShareIds: dynamicKeyShareIds,
|
|
1094
|
+
requiresSignedSessionId: false
|
|
1095
|
+
});
|
|
1096
|
+
} else if (externalKeyShareIds.length > 0) {
|
|
1097
|
+
this.logger.debug('Skipping recovery - only EXTERNAL shares available (backUpToClientShareService: false)');
|
|
1098
|
+
return []; // Return empty array since we can't recover EXTERNAL shares via API
|
|
1099
|
+
} else {
|
|
1100
|
+
this.logger.debug('No shares available for recovery');
|
|
1101
|
+
return []; // Return empty array if no shares are available
|
|
1102
|
+
}
|
|
829
1103
|
const dynamicKeyShares = data.keyShares.filter((keyShare)=>keyShare.encryptedAccountCredential !== null && keyShare.backupLocation === BackupLocation.DYNAMIC);
|
|
1104
|
+
var _wallet_externalServerKeySharesBackupInfo_passwordEncrypted;
|
|
1105
|
+
const isPasswordEncrypted = (_wallet_externalServerKeySharesBackupInfo_passwordEncrypted = (_wallet_externalServerKeySharesBackupInfo = wallet.externalServerKeySharesBackupInfo) == null ? void 0 : _wallet_externalServerKeySharesBackupInfo.passwordEncrypted) != null ? _wallet_externalServerKeySharesBackupInfo_passwordEncrypted : false;
|
|
1106
|
+
if (isPasswordEncrypted && !password) {
|
|
1107
|
+
throw new Error('Password is required for decryption but not provided. This backup was encrypted with a password.');
|
|
1108
|
+
}
|
|
830
1109
|
const decryptedKeyShares = await Promise.all(dynamicKeyShares.map((keyShare)=>this.decryptKeyShare({
|
|
831
1110
|
keyShare: keyShare.encryptedAccountCredential,
|
|
832
1111
|
password: password != null ? password : this.environmentId
|
|
@@ -856,6 +1135,7 @@ class DynamicWalletClient {
|
|
|
856
1135
|
* @param walletOperation - The wallet operation that determines required fields
|
|
857
1136
|
* @returns boolean indicating if wallet needs to be re-fetched and restored from server
|
|
858
1137
|
*/ async checkWalletFields({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD, shareCount }) {
|
|
1138
|
+
var _existingWallet_externalServerKeyShares;
|
|
859
1139
|
let keyshareCheck = false;
|
|
860
1140
|
let walletCheck = false;
|
|
861
1141
|
let thresholdSignatureSchemeCheck = false;
|
|
@@ -875,7 +1155,7 @@ class DynamicWalletClient {
|
|
|
875
1155
|
}
|
|
876
1156
|
// check if wallet already exists with sufficient keyshares
|
|
877
1157
|
if (existingWallet) {
|
|
878
|
-
var
|
|
1158
|
+
var _existingWallet_externalServerKeyShares1;
|
|
879
1159
|
const { shares } = this.recoverStrategy({
|
|
880
1160
|
externalServerKeySharesBackupInfo: existingWallet.externalServerKeySharesBackupInfo || {
|
|
881
1161
|
backups: getExternalServerKeyShareBackupInfo()
|
|
@@ -885,11 +1165,35 @@ class DynamicWalletClient {
|
|
|
885
1165
|
shareCount
|
|
886
1166
|
});
|
|
887
1167
|
const { dynamic: requiredDynamicKeyShareIds = [] } = shares;
|
|
888
|
-
|
|
1168
|
+
const { external: requiredExternalKeyShareIds = [] } = shares;
|
|
1169
|
+
// Check if we have enough shares from any backup location
|
|
1170
|
+
const totalRequiredShares = requiredDynamicKeyShareIds.length + requiredExternalKeyShareIds.length;
|
|
1171
|
+
// Check if we have the required shares either loaded OR available in backup
|
|
1172
|
+
const hasLoadedShares = totalRequiredShares <= (((_existingWallet_externalServerKeyShares1 = existingWallet.externalServerKeyShares) == null ? void 0 : _existingWallet_externalServerKeyShares1.length) || 0);
|
|
1173
|
+
// Check if backup contains the specific required share IDs
|
|
1174
|
+
const hasBackupShares = existingWallet.externalServerKeySharesBackupInfo && (()=>{
|
|
1175
|
+
const backupShares = existingWallet.externalServerKeySharesBackupInfo.backups.dynamic;
|
|
1176
|
+
const allRequiredIds = [
|
|
1177
|
+
...requiredDynamicKeyShareIds,
|
|
1178
|
+
...requiredExternalKeyShareIds
|
|
1179
|
+
];
|
|
1180
|
+
return allRequiredIds.every((requiredId)=>backupShares.some((backupShare)=>backupShare.externalKeyShareId === requiredId || backupShare.keyShareId === requiredId));
|
|
1181
|
+
})();
|
|
1182
|
+
if (hasLoadedShares || hasBackupShares) {
|
|
889
1183
|
keyshareCheck = true;
|
|
890
1184
|
}
|
|
891
1185
|
}
|
|
892
|
-
|
|
1186
|
+
const result = walletCheck && thresholdSignatureSchemeCheck && keyshareCheck && derivationPathCheck;
|
|
1187
|
+
this.logger.debug('Wallet checks:', {
|
|
1188
|
+
walletCheck,
|
|
1189
|
+
thresholdSignatureSchemeCheck,
|
|
1190
|
+
keyshareCheck,
|
|
1191
|
+
derivationPathCheck,
|
|
1192
|
+
existingWallet: !!existingWallet,
|
|
1193
|
+
keySharesLength: existingWallet == null ? void 0 : (_existingWallet_externalServerKeyShares = existingWallet.externalServerKeyShares) == null ? void 0 : _existingWallet_externalServerKeyShares.length,
|
|
1194
|
+
result
|
|
1195
|
+
});
|
|
1196
|
+
return result;
|
|
893
1197
|
}
|
|
894
1198
|
/**
|
|
895
1199
|
* verifyPassword attempts to recover and decrypt a single client key share using the provided password.
|
|
@@ -915,8 +1219,10 @@ class DynamicWalletClient {
|
|
|
915
1219
|
accountAddress
|
|
916
1220
|
});
|
|
917
1221
|
const { dynamic: dynamicKeyShareIds = [] } = backups;
|
|
918
|
-
|
|
919
|
-
|
|
1222
|
+
const { external: externalKeyShareIds = [] } = backups;
|
|
1223
|
+
// Check if we have any shares available (DYNAMIC or EXTERNAL)
|
|
1224
|
+
if (dynamicKeyShareIds.length === 0 && externalKeyShareIds.length === 0) {
|
|
1225
|
+
throw new Error('No key shares found');
|
|
920
1226
|
}
|
|
921
1227
|
try {
|
|
922
1228
|
await this.recoverEncryptedBackupByWallet({
|
|
@@ -961,88 +1267,164 @@ class DynamicWalletClient {
|
|
|
961
1267
|
if (walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.RESHARE) {
|
|
962
1268
|
return true;
|
|
963
1269
|
}
|
|
964
|
-
const { requiredShareCount } = this.recoverStrategy({
|
|
1270
|
+
const { shares, requiredShareCount } = this.recoverStrategy({
|
|
965
1271
|
externalServerKeySharesBackupInfo,
|
|
966
1272
|
thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme,
|
|
967
1273
|
walletOperation
|
|
968
1274
|
});
|
|
969
|
-
|
|
1275
|
+
const dynamicKeyShareIds = shares[BackupLocation.DYNAMIC] || [];
|
|
1276
|
+
const externalKeyShareIds = shares[BackupLocation.EXTERNAL] || [];
|
|
1277
|
+
// Check if we have enough shares from either location
|
|
1278
|
+
const totalAvailableShares = externalServerKeyShares.length + dynamicKeyShareIds.length + externalKeyShareIds.length;
|
|
1279
|
+
if (totalAvailableShares >= requiredShareCount) {
|
|
970
1280
|
return false;
|
|
971
1281
|
}
|
|
972
1282
|
return true;
|
|
973
1283
|
}
|
|
974
1284
|
async getWalletExternalServerKeyShareBackupInfo({ accountAddress }) {
|
|
975
|
-
var
|
|
1285
|
+
var _wallet_externalServerKeySharesBackupInfo_backups_BackupLocation_DYNAMIC, _wallet_externalServerKeySharesBackupInfo_backups, _wallet_externalServerKeySharesBackupInfo, _user_verifiedCredentials;
|
|
976
1286
|
this.ensureApiClientAuthenticated();
|
|
1287
|
+
let wallet = this.walletMap[accountAddress];
|
|
1288
|
+
if (!wallet) {
|
|
1289
|
+
const fetchedWallet = await this.getWalletByAddress(accountAddress);
|
|
1290
|
+
if (!fetchedWallet) {
|
|
1291
|
+
throw new Error(`Wallet not found for address 2: ${accountAddress}`);
|
|
1292
|
+
}
|
|
1293
|
+
wallet = fetchedWallet;
|
|
1294
|
+
}
|
|
977
1295
|
// Return existing backup info if it exists
|
|
978
|
-
if (((
|
|
979
|
-
return
|
|
1296
|
+
if (((_wallet_externalServerKeySharesBackupInfo = wallet.externalServerKeySharesBackupInfo) == null ? void 0 : (_wallet_externalServerKeySharesBackupInfo_backups = _wallet_externalServerKeySharesBackupInfo.backups) == null ? void 0 : (_wallet_externalServerKeySharesBackupInfo_backups_BackupLocation_DYNAMIC = _wallet_externalServerKeySharesBackupInfo_backups[BackupLocation.DYNAMIC]) == null ? void 0 : _wallet_externalServerKeySharesBackupInfo_backups_BackupLocation_DYNAMIC.length) > 0) {
|
|
1297
|
+
return wallet.externalServerKeySharesBackupInfo;
|
|
980
1298
|
}
|
|
981
1299
|
// Get backup info from server
|
|
982
|
-
const user = await this.apiClient.getUser();
|
|
983
|
-
const
|
|
1300
|
+
const user = await this.apiClient.getUser(v4());
|
|
1301
|
+
const walletData = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
984
1302
|
return getExternalServerKeyShareBackupInfo({
|
|
985
|
-
walletProperties:
|
|
1303
|
+
walletProperties: walletData == null ? void 0 : walletData.walletProperties
|
|
986
1304
|
});
|
|
987
1305
|
}
|
|
988
1306
|
async getWallet({ accountAddress, walletOperation = WalletOperation.NO_OPERATION, shareCount = undefined, password = undefined }) {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
accountAddress,
|
|
993
|
-
walletOperation,
|
|
994
|
-
shareCount
|
|
995
|
-
});
|
|
996
|
-
if (existingWalletCheck) {
|
|
997
|
-
this.logger.debug(`Wallet ${accountAddress} already exists`);
|
|
998
|
-
return this.walletMap[accountAddress];
|
|
999
|
-
}
|
|
1000
|
-
//todo: Question - why don't we just call getWallets here? so then all are preloaded
|
|
1001
|
-
// Fetch and restore wallet from server
|
|
1002
|
-
const user = await this.apiClient.getUser();
|
|
1003
|
-
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
1004
|
-
this.logger.debug('Restoring wallet', wallet);
|
|
1005
|
-
const walletProperties = wallet.walletProperties;
|
|
1006
|
-
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1007
|
-
walletId: wallet.id,
|
|
1008
|
-
chainName: wallet.chainName,
|
|
1009
|
-
accountAddress,
|
|
1010
|
-
thresholdSignatureScheme: walletProperties.thresholdSignatureScheme,
|
|
1011
|
-
derivationPath: walletProperties.derivationPath,
|
|
1012
|
-
externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo({
|
|
1013
|
-
walletProperties
|
|
1014
|
-
})
|
|
1015
|
-
});
|
|
1016
|
-
if (walletOperation !== WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
|
|
1017
|
-
accountAddress,
|
|
1018
|
-
walletOperation
|
|
1019
|
-
})) {
|
|
1020
|
-
const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
|
|
1307
|
+
try {
|
|
1308
|
+
this.ensureApiClientAuthenticated();
|
|
1309
|
+
const existingWalletCheck = await this.checkWalletFields({
|
|
1021
1310
|
accountAddress,
|
|
1022
|
-
|
|
1023
|
-
walletOperation: walletOperation,
|
|
1311
|
+
walletOperation,
|
|
1024
1312
|
shareCount
|
|
1025
1313
|
});
|
|
1026
|
-
|
|
1314
|
+
if (existingWalletCheck) {
|
|
1315
|
+
this.logger.debug(`Wallet ${accountAddress} already exists`);
|
|
1316
|
+
return this.walletMap[accountAddress];
|
|
1317
|
+
}
|
|
1318
|
+
const wallet = await this.getWalletByAddress(accountAddress);
|
|
1319
|
+
if (!wallet) {
|
|
1320
|
+
throw new Error(`Wallet not found for address 3: ${accountAddress}`);
|
|
1321
|
+
}
|
|
1322
|
+
this.logger.debug('Restoring wallet', wallet);
|
|
1323
|
+
// The wallet is already processed, so we can use it directly
|
|
1324
|
+
this.walletMap[accountAddress] = wallet;
|
|
1325
|
+
if (walletOperation !== WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
|
|
1326
|
+
accountAddress,
|
|
1327
|
+
walletOperation
|
|
1328
|
+
})) {
|
|
1329
|
+
const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
|
|
1330
|
+
accountAddress,
|
|
1331
|
+
password: password != null ? password : this.environmentId,
|
|
1332
|
+
walletOperation: walletOperation,
|
|
1333
|
+
shareCount
|
|
1334
|
+
});
|
|
1335
|
+
this.logger.debug('[DynamicWaasWalletClient] Recovered backup', decryptedKeyShares);
|
|
1336
|
+
}
|
|
1337
|
+
// externalServerKeyShares
|
|
1338
|
+
const walletCount = Object.keys(this.walletMap).length;
|
|
1339
|
+
if (walletCount === 0) {
|
|
1340
|
+
throw new Error('No wallets found');
|
|
1341
|
+
}
|
|
1342
|
+
// Return the only wallet if there's just one
|
|
1343
|
+
if (walletCount === 1) {
|
|
1344
|
+
return Object.values(this.walletMap)[0];
|
|
1345
|
+
}
|
|
1346
|
+
return this.walletMap[accountAddress];
|
|
1347
|
+
} catch (error) {
|
|
1348
|
+
this.logger.error('Error in getWallet', error);
|
|
1349
|
+
throw error;
|
|
1027
1350
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Get a single wallet by address
|
|
1354
|
+
* First tries the efficient getWaasWalletByAddress endpoint, falls back to getUser() if not available
|
|
1355
|
+
*/ async getWalletByAddress(accountAddress) {
|
|
1356
|
+
// Return cached wallet if available
|
|
1357
|
+
if (this.walletMap[accountAddress]) {
|
|
1358
|
+
return this.walletMap[accountAddress];
|
|
1031
1359
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1360
|
+
this.ensureApiClientAuthenticated();
|
|
1361
|
+
// Try getting single wallet by address first
|
|
1362
|
+
try {
|
|
1363
|
+
var _walletResponse_wallet;
|
|
1364
|
+
const walletResponse = await this.apiClient.getWaasWalletByAddress({
|
|
1365
|
+
walletAddress: accountAddress
|
|
1366
|
+
});
|
|
1367
|
+
const walletProperties = {
|
|
1368
|
+
walletId: walletResponse.wallet.walletId,
|
|
1369
|
+
chainName: walletResponse.wallet.chainName,
|
|
1370
|
+
accountAddress: walletResponse.wallet.accountAddress,
|
|
1371
|
+
externalServerKeyShares: [],
|
|
1372
|
+
derivationPath: walletResponse.wallet.derivationPath,
|
|
1373
|
+
thresholdSignatureScheme: walletResponse.wallet.thresholdSignatureScheme,
|
|
1374
|
+
externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo({
|
|
1375
|
+
walletProperties: {
|
|
1376
|
+
// @ts-expect-error TODO: update response to get key shares
|
|
1377
|
+
keyShares: ((_walletResponse_wallet = walletResponse.wallet) == null ? void 0 : _walletResponse_wallet.keyShares) || [],
|
|
1378
|
+
thresholdSignatureScheme: walletResponse.wallet.thresholdSignatureScheme,
|
|
1379
|
+
derivationPath: walletResponse.wallet.derivationPath
|
|
1380
|
+
}
|
|
1381
|
+
})
|
|
1382
|
+
};
|
|
1383
|
+
// Cache the wallet
|
|
1384
|
+
this.walletMap[accountAddress] = walletProperties;
|
|
1385
|
+
return walletProperties;
|
|
1386
|
+
} catch (error) {
|
|
1387
|
+
var _error_response;
|
|
1388
|
+
// If the new endpoint doesn't exist (404 or not implemented), fall back to getUser()
|
|
1389
|
+
if ((error == null ? void 0 : (_error_response = error.response) == null ? void 0 : _error_response.status) === 404 || (error == null ? void 0 : error.code) === 'ERR_BAD_REQUEST') {
|
|
1390
|
+
var _user_verifiedCredentials, _wallet_walletProperties, _wallet_walletProperties1;
|
|
1391
|
+
this.logger.debug('getWaasWalletByAddress endpoint not available, falling back to getUser()');
|
|
1392
|
+
// Fallback to getUser() approach
|
|
1393
|
+
const user = await this.apiClient.getUser(v4());
|
|
1394
|
+
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.walletName === 'dynamicwaas' && vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
1395
|
+
if (!wallet) {
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
var _wallet_walletProperties_derivationPath;
|
|
1399
|
+
const walletProperties = {
|
|
1400
|
+
walletId: wallet.id,
|
|
1401
|
+
chainName: wallet.chain,
|
|
1402
|
+
accountAddress: wallet.address,
|
|
1403
|
+
externalServerKeyShares: [],
|
|
1404
|
+
derivationPath: (_wallet_walletProperties_derivationPath = (_wallet_walletProperties = wallet.walletProperties) == null ? void 0 : _wallet_walletProperties.derivationPath) != null ? _wallet_walletProperties_derivationPath : undefined,
|
|
1405
|
+
thresholdSignatureScheme: (_wallet_walletProperties1 = wallet.walletProperties) == null ? void 0 : _wallet_walletProperties1.thresholdSignatureScheme,
|
|
1406
|
+
externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo({
|
|
1407
|
+
walletProperties: wallet.walletProperties || {}
|
|
1408
|
+
})
|
|
1409
|
+
};
|
|
1410
|
+
// Cache the wallet
|
|
1411
|
+
this.walletMap[accountAddress] = walletProperties;
|
|
1412
|
+
return walletProperties;
|
|
1413
|
+
}
|
|
1414
|
+
throw error;
|
|
1035
1415
|
}
|
|
1036
|
-
return this.walletMap[accountAddress];
|
|
1037
1416
|
}
|
|
1038
|
-
|
|
1417
|
+
/**
|
|
1418
|
+
* Get all wallets (kept for backward compatibility but now uses lazy loading)
|
|
1419
|
+
* @deprecated Consider using getWalletByAddress for better performance with large wallet counts
|
|
1420
|
+
*/ async getWallets() {
|
|
1039
1421
|
var _user_verifiedCredentials;
|
|
1040
1422
|
this.ensureApiClientAuthenticated();
|
|
1041
|
-
const user = await this.apiClient.getUser();
|
|
1423
|
+
const user = await this.apiClient.getUser(v4());
|
|
1042
1424
|
const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
|
|
1043
1425
|
const wallets = waasWallets.map((vc)=>{
|
|
1044
|
-
var _this_walletMap_vc_address,
|
|
1045
|
-
var
|
|
1426
|
+
var _this_walletMap_vc_address, _vc_walletProperties, _vc_walletProperties1;
|
|
1427
|
+
var _vc_walletProperties_derivationPath;
|
|
1046
1428
|
return {
|
|
1047
1429
|
walletId: vc.id,
|
|
1048
1430
|
chainName: vc.chain,
|
|
@@ -1051,37 +1433,223 @@ class DynamicWalletClient {
|
|
|
1051
1433
|
walletProperties: vc.walletProperties || {}
|
|
1052
1434
|
}),
|
|
1053
1435
|
externalServerKeyShares: ((_this_walletMap_vc_address = this.walletMap[vc.address]) == null ? void 0 : _this_walletMap_vc_address.externalServerKeyShares) || [],
|
|
1054
|
-
derivationPath: (
|
|
1055
|
-
thresholdSignatureScheme: (
|
|
1436
|
+
derivationPath: (_vc_walletProperties_derivationPath = (_vc_walletProperties = vc.walletProperties) == null ? void 0 : _vc_walletProperties.derivationPath) != null ? _vc_walletProperties_derivationPath : undefined,
|
|
1437
|
+
thresholdSignatureScheme: (_vc_walletProperties1 = vc.walletProperties) == null ? void 0 : _vc_walletProperties1.thresholdSignatureScheme
|
|
1056
1438
|
};
|
|
1057
1439
|
});
|
|
1058
1440
|
this.walletMap = wallets.reduce((acc, wallet)=>{
|
|
1059
|
-
var _acc_accountAddress;
|
|
1060
|
-
const accountAddress = wallet.accountAddress;
|
|
1061
1441
|
acc[wallet.accountAddress] = {
|
|
1062
1442
|
walletId: wallet.walletId,
|
|
1063
1443
|
chainName: wallet.chainName,
|
|
1064
1444
|
accountAddress: wallet.accountAddress,
|
|
1065
1445
|
externalServerKeyShares: wallet.externalServerKeyShares || [],
|
|
1066
1446
|
externalServerKeySharesBackupInfo: wallet.externalServerKeySharesBackupInfo,
|
|
1067
|
-
derivationPath:
|
|
1447
|
+
derivationPath: wallet.derivationPath,
|
|
1068
1448
|
thresholdSignatureScheme: wallet.thresholdSignatureScheme
|
|
1069
1449
|
};
|
|
1070
1450
|
return acc;
|
|
1071
1451
|
}, {});
|
|
1072
1452
|
return wallets;
|
|
1073
1453
|
}
|
|
1074
|
-
constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, debug }){
|
|
1454
|
+
constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, debug, forwardMPCClient, enableMPCAccelerator = false }){
|
|
1075
1455
|
this.logger = logger;
|
|
1076
1456
|
this.walletMap = {} // todo: store in session storage
|
|
1077
1457
|
;
|
|
1078
1458
|
this.isApiClientAuthenticated = false;
|
|
1459
|
+
this.forwardMPCEnabled = false;
|
|
1079
1460
|
this.environmentId = environmentId;
|
|
1080
1461
|
this.baseMPCRelayApiUrl = baseMPCRelayApiUrl;
|
|
1081
1462
|
this.baseApiUrl = baseApiUrl;
|
|
1082
1463
|
this.debug = Boolean(debug);
|
|
1083
1464
|
this.logger.setLogLevel(this.debug ? 'DEBUG' : DEFAULT_LOG_LEVEL);
|
|
1465
|
+
this.forwardMPCEnabled = enableMPCAccelerator || Boolean(forwardMPCClient);
|
|
1466
|
+
// Initialize API client with forwardMPCClient
|
|
1467
|
+
this.apiClient = new DynamicApiClient({
|
|
1468
|
+
environmentId,
|
|
1469
|
+
baseApiUrl,
|
|
1470
|
+
forwardMPCClient
|
|
1471
|
+
});
|
|
1472
|
+
if (this.forwardMPCEnabled) {
|
|
1473
|
+
this.logger.info('Initializing ForwardMPC enclave websocket in node client. Environment: ' + this.environmentId);
|
|
1474
|
+
this.initializeForwardMPCClient();
|
|
1475
|
+
}
|
|
1084
1476
|
}
|
|
1085
1477
|
}
|
|
1086
1478
|
|
|
1087
|
-
|
|
1479
|
+
const createCore = ({ environmentId, baseApiUrl, baseMPCRelayApiUrl, debug = false })=>{
|
|
1480
|
+
const coreLogger = logger;
|
|
1481
|
+
coreLogger.setLogLevel(debug ? 'DEBUG' : DEFAULT_LOG_LEVEL);
|
|
1482
|
+
const createApiClient = (options = {})=>{
|
|
1483
|
+
return new DynamicApiClient(_extends({
|
|
1484
|
+
environmentId,
|
|
1485
|
+
baseApiUrl
|
|
1486
|
+
}, options));
|
|
1487
|
+
};
|
|
1488
|
+
return {
|
|
1489
|
+
environmentId,
|
|
1490
|
+
createApiClient,
|
|
1491
|
+
logger: coreLogger,
|
|
1492
|
+
baseMPCRelayApiUrl,
|
|
1493
|
+
baseApiUrl,
|
|
1494
|
+
debug
|
|
1495
|
+
};
|
|
1496
|
+
};
|
|
1497
|
+
|
|
1498
|
+
// Helper function to create API client for delegated operations
|
|
1499
|
+
const createDelegatedApiClient = (client, options = {})=>{
|
|
1500
|
+
return client.createApiClient(options);
|
|
1501
|
+
};
|
|
1502
|
+
// Helper function to create API client with wallet-specific headers
|
|
1503
|
+
const createDelegatedApiClientWithWalletKey = (client, walletApiKey)=>{
|
|
1504
|
+
const apiClient = client.createApiClient();
|
|
1505
|
+
// Add the wallet-specific API key header to all requests from this client
|
|
1506
|
+
apiClient.apiClient.defaults.headers['x-dyn-wallet-api-key'] = walletApiKey;
|
|
1507
|
+
return apiClient;
|
|
1508
|
+
};
|
|
1509
|
+
const createDelegatedWalletClient = ({ environmentId, baseApiUrl, baseMPCRelayApiUrl, apiKey, debug = false })=>{
|
|
1510
|
+
const core = createCore({
|
|
1511
|
+
environmentId,
|
|
1512
|
+
baseApiUrl,
|
|
1513
|
+
baseMPCRelayApiUrl,
|
|
1514
|
+
debug
|
|
1515
|
+
});
|
|
1516
|
+
// Store the API key for use in delegated operations
|
|
1517
|
+
core.apiKey = apiKey;
|
|
1518
|
+
const walletMap = {};
|
|
1519
|
+
const client = {
|
|
1520
|
+
get environmentId () {
|
|
1521
|
+
return core.environmentId;
|
|
1522
|
+
},
|
|
1523
|
+
get debug () {
|
|
1524
|
+
return core.debug;
|
|
1525
|
+
},
|
|
1526
|
+
get apiUrl () {
|
|
1527
|
+
var _core_baseApiUrl;
|
|
1528
|
+
return (_core_baseApiUrl = core.baseApiUrl) != null ? _core_baseApiUrl : 'https://app.dynamicauth.com';
|
|
1529
|
+
},
|
|
1530
|
+
get wallets () {
|
|
1531
|
+
return walletMap;
|
|
1532
|
+
},
|
|
1533
|
+
createApiClient: (options = {})=>{
|
|
1534
|
+
return core.createApiClient(_extends({
|
|
1535
|
+
authToken: apiKey
|
|
1536
|
+
}, options));
|
|
1537
|
+
},
|
|
1538
|
+
logger: core.logger,
|
|
1539
|
+
apiKey,
|
|
1540
|
+
baseMPCRelayApiUrl: core.baseMPCRelayApiUrl
|
|
1541
|
+
};
|
|
1542
|
+
return client;
|
|
1543
|
+
};
|
|
1544
|
+
|
|
1545
|
+
const dynamicDelegatedSign = async (client, { walletId, message, isFormatted, walletApiKey, onError, context })=>{
|
|
1546
|
+
const dynamicRequestId = v4();
|
|
1547
|
+
// Convert message to hex if it's a Uint8Array
|
|
1548
|
+
if (typeof message !== 'string') {
|
|
1549
|
+
message = '0x' + Buffer.from(message).toString('hex');
|
|
1550
|
+
}
|
|
1551
|
+
const apiClient = createDelegatedApiClientWithWalletKey(client, walletApiKey);
|
|
1552
|
+
const data = await apiClient.delegatedSignMessage({
|
|
1553
|
+
walletId,
|
|
1554
|
+
message,
|
|
1555
|
+
isFormatted,
|
|
1556
|
+
dynamicRequestId,
|
|
1557
|
+
onError,
|
|
1558
|
+
context: context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined
|
|
1559
|
+
});
|
|
1560
|
+
return data;
|
|
1561
|
+
};
|
|
1562
|
+
const delegatedSign = async (client, { chainName, message, roomId, keyShare, derivationPath, isFormatted })=>{
|
|
1563
|
+
try {
|
|
1564
|
+
const mpcSigner = getMPCSigner({
|
|
1565
|
+
chainName,
|
|
1566
|
+
baseRelayUrl: client.baseMPCRelayApiUrl
|
|
1567
|
+
});
|
|
1568
|
+
const formattedMessage = isFormatted ? new MessageHash(message) : formatMessage(chainName, message);
|
|
1569
|
+
const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath);
|
|
1570
|
+
return signature;
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
client.logger.error('Error in delegatedSign', error);
|
|
1573
|
+
throw error;
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
const delegatedSignMessage = async (client, { walletId, walletApiKey, keyShare, message, chainName, isFormatted = false, onError, context })=>{
|
|
1577
|
+
// Validate required parameters
|
|
1578
|
+
if (!keyShare) {
|
|
1579
|
+
throw new Error('Delegated key share is required to sign a message');
|
|
1580
|
+
}
|
|
1581
|
+
const wallet = await getWallet(client, {
|
|
1582
|
+
walletId
|
|
1583
|
+
});
|
|
1584
|
+
// Perform the dynamic server sign
|
|
1585
|
+
const data = await dynamicDelegatedSign(client, {
|
|
1586
|
+
walletId,
|
|
1587
|
+
walletApiKey,
|
|
1588
|
+
message,
|
|
1589
|
+
isFormatted,
|
|
1590
|
+
onError,
|
|
1591
|
+
context
|
|
1592
|
+
});
|
|
1593
|
+
const derivationPath = wallet.derivationPath && wallet.derivationPath !== '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
|
|
1594
|
+
// Perform the external server sign and return the signature
|
|
1595
|
+
const signature = await delegatedSign(client, {
|
|
1596
|
+
chainName,
|
|
1597
|
+
message,
|
|
1598
|
+
roomId: data.roomId,
|
|
1599
|
+
keyShare,
|
|
1600
|
+
derivationPath,
|
|
1601
|
+
isFormatted
|
|
1602
|
+
});
|
|
1603
|
+
return signature;
|
|
1604
|
+
};
|
|
1605
|
+
// Helper function to get wallet (to be implemented in wallet module)
|
|
1606
|
+
const getWallet = async (client, { walletId })=>{
|
|
1607
|
+
const wallets = client.wallets;
|
|
1608
|
+
// Check if wallet is already cached
|
|
1609
|
+
if (wallets[walletId]) {
|
|
1610
|
+
return wallets[walletId];
|
|
1611
|
+
}
|
|
1612
|
+
// Fetch wallet from API
|
|
1613
|
+
const apiClient = createDelegatedApiClient(client);
|
|
1614
|
+
const { wallet } = await apiClient.getWaasWalletById({
|
|
1615
|
+
walletId
|
|
1616
|
+
});
|
|
1617
|
+
const waasWallet = {
|
|
1618
|
+
walletId,
|
|
1619
|
+
chainName: wallet.chainName,
|
|
1620
|
+
accountAddress: wallet.accountAddress,
|
|
1621
|
+
externalServerKeyShares: [],
|
|
1622
|
+
derivationPath: wallet.derivationPath,
|
|
1623
|
+
thresholdSignatureScheme: wallet.thresholdSignatureScheme,
|
|
1624
|
+
externalServerKeySharesBackupInfo: {
|
|
1625
|
+
passwordEncrypted: false,
|
|
1626
|
+
backups: {
|
|
1627
|
+
dynamic: [],
|
|
1628
|
+
googleDrive: [],
|
|
1629
|
+
iCloud: [],
|
|
1630
|
+
user: [],
|
|
1631
|
+
external: [],
|
|
1632
|
+
delegated: []
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
};
|
|
1636
|
+
// Cache the wallet
|
|
1637
|
+
wallets[walletId] = waasWallet;
|
|
1638
|
+
return waasWallet;
|
|
1639
|
+
};
|
|
1640
|
+
|
|
1641
|
+
const revokeDelegation = async (client, walletId)=>{
|
|
1642
|
+
try {
|
|
1643
|
+
// TODO: Uncomment when API method is implemented
|
|
1644
|
+
// const apiClient = createDelegatedApiClient(client);
|
|
1645
|
+
// await apiClient.revokeDelegation({
|
|
1646
|
+
// walletId,
|
|
1647
|
+
// });
|
|
1648
|
+
client.logger.info(`Delegation revoked for wallet: ${walletId}`);
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
client.logger.error('Error revoking delegation', error);
|
|
1651
|
+
throw new Error('Failed to revoke delegation');
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
export { DynamicWalletClient, base64ToBytes, bytesToBase64, createDelegatedWalletClient, delegatedSignMessage, ensureBase64Padding, formatEvmMessage, formatMessage, getExternalServerKeyShareBackupInfo, getMPCSignatureScheme, getMPCSigner, isHexString, mergeUniqueKeyShares, retryPromise, revokeDelegation, stringToBytes };
|