@dynamic-labs-wallet/node 0.0.0-preview.114.4 → 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 +790 -233
- package/index.esm.js +778 -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/ecdsa.d.ts +1 -1
- package/internal/core/ed25519.cjs +1 -0
- package/internal/core/ed25519_exportable.cjs +1 -0
- package/internal/core/ed25519_exportable.d.ts +21 -0
- package/internal/core/index.cjs +1 -0
- package/internal/core/index.d.ts +4 -2
- package/internal/core/native.d.ts +65 -0
- package/internal/core/sr25519.cjs +1 -0
- package/internal/core/sr25519.d.ts +22 -0
- package/internal/core/types.cjs +1 -0
- package/internal/core/types.d.ts +20 -0
- package/internal/node/index.cjs +1 -0
- package/internal/node/index.d.ts +8 -2
- package/internal/node/native/libmpc_executor_linux_arm64_nodejs.node +0 -0
- package/internal/node/native/libmpc_executor_linux_x86_64_nodejs.node +0 -0
- package/internal/node/native/libmpc_executor_macos_arm64_nodejs.node +0 -0
- package/internal/node/native/libmpc_executor_macos_x86_64_nodejs.node +0 -0
- package/internal/node/native.cjs +1 -0
- package/internal/node/native.d.ts +1 -1
- package/package.json +13 -8
- 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 +85 -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/index.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);
|
|
@@ -689,11 +866,28 @@ class DynamicWalletClient {
|
|
|
689
866
|
const serializedEncryptedKeyShare = Buffer.from(JSON.stringify(encryptedKeyShare)).toString('base64');
|
|
690
867
|
return serializedEncryptedKeyShare;
|
|
691
868
|
}
|
|
692
|
-
async
|
|
869
|
+
async ensureCeremonyCompletionBeforeBackup({ accountAddress }) {
|
|
870
|
+
let retries = 0;
|
|
871
|
+
const maxRetries = 3;
|
|
872
|
+
while((!this.walletMap[accountAddress] || !this.walletMap[accountAddress].walletId) && retries < maxRetries){
|
|
873
|
+
await new Promise((resolve)=>setTimeout(resolve, 1000)); // Wait 1 second
|
|
874
|
+
retries++;
|
|
875
|
+
}
|
|
876
|
+
if (!this.walletMap[accountAddress] || !this.walletMap[accountAddress].walletId) {
|
|
877
|
+
throw new Error('Ceremony completion timeout');
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
async storeEncryptedBackupByWallet({ accountAddress, externalServerKeyShares = undefined, password = undefined, backUpToClientShareService }) {
|
|
693
881
|
this.ensureApiClientAuthenticated();
|
|
882
|
+
//add retry logic for ceremony completion to prevent race condition
|
|
883
|
+
await this.ensureCeremonyCompletionBeforeBackup({
|
|
884
|
+
accountAddress
|
|
885
|
+
});
|
|
886
|
+
const dynamicRequestId = v4();
|
|
694
887
|
try {
|
|
695
888
|
const keySharesToBackup = externalServerKeyShares != null ? externalServerKeyShares : await this.getExternalServerKeyShares({
|
|
696
|
-
accountAddress
|
|
889
|
+
accountAddress,
|
|
890
|
+
password
|
|
697
891
|
});
|
|
698
892
|
if (!keySharesToBackup || keySharesToBackup.length === 0) {
|
|
699
893
|
throw new Error(`Key shares not found for accountAddress: ${accountAddress}`);
|
|
@@ -705,51 +899,92 @@ class DynamicWalletClient {
|
|
|
705
899
|
if (!this.walletMap[accountAddress] || !this.walletMap[accountAddress].walletId) {
|
|
706
900
|
throw new Error(`WalletId not found for accountAddress: ${accountAddress}`);
|
|
707
901
|
}
|
|
708
|
-
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({
|
|
709
924
|
walletId: this.walletMap[accountAddress].walletId,
|
|
710
|
-
|
|
711
|
-
|
|
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
|
+
}
|
|
712
939
|
});
|
|
713
940
|
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
714
|
-
externalServerKeySharesBackupInfo:
|
|
715
|
-
walletProperties: {
|
|
716
|
-
derivationPath: this.walletMap[accountAddress].derivationPath,
|
|
717
|
-
keyShares: data.keyShares,
|
|
718
|
-
thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
|
|
719
|
-
}
|
|
720
|
-
})
|
|
941
|
+
externalServerKeySharesBackupInfo: updatedBackupInfo
|
|
721
942
|
});
|
|
722
|
-
return
|
|
943
|
+
return encryptedKeyShares;
|
|
723
944
|
} catch (error) {
|
|
724
|
-
this.logger.error('Error in storeEncryptedBackupByWallet:',
|
|
945
|
+
this.logger.error('Error in storeEncryptedBackupByWallet:', {
|
|
946
|
+
accountAddress,
|
|
947
|
+
error,
|
|
948
|
+
dynamicRequestId
|
|
949
|
+
});
|
|
725
950
|
throw error;
|
|
726
951
|
}
|
|
727
952
|
}
|
|
728
|
-
async storeEncryptedBackupByWalletWithRetry({ accountAddress, externalServerKeyShares, password }) {
|
|
953
|
+
async storeEncryptedBackupByWalletWithRetry({ accountAddress, externalServerKeyShares, password, backUpToClientShareService }) {
|
|
729
954
|
await retryPromise(()=>this.storeEncryptedBackupByWallet({
|
|
730
955
|
accountAddress,
|
|
731
956
|
externalServerKeyShares,
|
|
732
|
-
password
|
|
957
|
+
password,
|
|
958
|
+
backUpToClientShareService
|
|
733
959
|
}), {
|
|
734
960
|
operationName: 'store encrypted backup',
|
|
735
961
|
logContext: {
|
|
736
|
-
walletAddress: accountAddress
|
|
737
|
-
keyShares: externalServerKeyShares == null ? void 0 : externalServerKeyShares.map((keyShare)=>this.encryptKeyShare({
|
|
738
|
-
keyShare,
|
|
739
|
-
password
|
|
740
|
-
}))
|
|
962
|
+
walletAddress: accountAddress
|
|
741
963
|
}
|
|
742
964
|
});
|
|
743
965
|
}
|
|
744
966
|
async getExternalServerKeyShares({ accountAddress, password }) {
|
|
967
|
+
var _wallet_externalServerKeySharesBackupInfo_backups, _wallet_externalServerKeySharesBackupInfo;
|
|
745
968
|
const wallet = await this.getWallet({
|
|
746
969
|
accountAddress,
|
|
747
970
|
password,
|
|
748
971
|
walletOperation: WalletOperation.REACH_THRESHOLD
|
|
749
972
|
});
|
|
750
|
-
|
|
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;
|
|
751
986
|
}
|
|
752
|
-
async updatePassword({ accountAddress, existingPassword, newPassword }) {
|
|
987
|
+
async updatePassword({ accountAddress, existingPassword, newPassword, backUpToClientShareService }) {
|
|
753
988
|
await this.getWallet({
|
|
754
989
|
accountAddress,
|
|
755
990
|
password: existingPassword,
|
|
@@ -757,7 +992,8 @@ class DynamicWalletClient {
|
|
|
757
992
|
});
|
|
758
993
|
await this.storeEncryptedBackupByWallet({
|
|
759
994
|
accountAddress,
|
|
760
|
-
password: newPassword
|
|
995
|
+
password: newPassword,
|
|
996
|
+
backUpToClientShareService
|
|
761
997
|
});
|
|
762
998
|
}
|
|
763
999
|
async decryptKeyShare({ keyShare, password }) {
|
|
@@ -788,17 +1024,57 @@ class DynamicWalletClient {
|
|
|
788
1024
|
if (shareCount !== undefined) {
|
|
789
1025
|
requiredShareCount = shareCount;
|
|
790
1026
|
}
|
|
791
|
-
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;
|
|
792
1032
|
return {
|
|
793
1033
|
shares: {
|
|
794
|
-
[
|
|
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
|
+
})
|
|
795
1038
|
},
|
|
796
1039
|
requiredShareCount
|
|
797
1040
|
};
|
|
798
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
|
+
}
|
|
799
1067
|
async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, shareCount = undefined, storeRecoveredShares = true }) {
|
|
1068
|
+
var _wallet_externalServerKeySharesBackupInfo;
|
|
800
1069
|
this.ensureApiClientAuthenticated();
|
|
801
|
-
|
|
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
|
+
}
|
|
802
1078
|
this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, wallet);
|
|
803
1079
|
const { shares } = this.recoverStrategy({
|
|
804
1080
|
externalServerKeySharesBackupInfo: wallet.externalServerKeySharesBackupInfo,
|
|
@@ -806,12 +1082,30 @@ class DynamicWalletClient {
|
|
|
806
1082
|
walletOperation,
|
|
807
1083
|
shareCount
|
|
808
1084
|
});
|
|
809
|
-
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
+
}
|
|
814
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
|
+
}
|
|
815
1109
|
const decryptedKeyShares = await Promise.all(dynamicKeyShares.map((keyShare)=>this.decryptKeyShare({
|
|
816
1110
|
keyShare: keyShare.encryptedAccountCredential,
|
|
817
1111
|
password: password != null ? password : this.environmentId
|
|
@@ -841,6 +1135,7 @@ class DynamicWalletClient {
|
|
|
841
1135
|
* @param walletOperation - The wallet operation that determines required fields
|
|
842
1136
|
* @returns boolean indicating if wallet needs to be re-fetched and restored from server
|
|
843
1137
|
*/ async checkWalletFields({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD, shareCount }) {
|
|
1138
|
+
var _existingWallet_externalServerKeyShares;
|
|
844
1139
|
let keyshareCheck = false;
|
|
845
1140
|
let walletCheck = false;
|
|
846
1141
|
let thresholdSignatureSchemeCheck = false;
|
|
@@ -860,7 +1155,7 @@ class DynamicWalletClient {
|
|
|
860
1155
|
}
|
|
861
1156
|
// check if wallet already exists with sufficient keyshares
|
|
862
1157
|
if (existingWallet) {
|
|
863
|
-
var
|
|
1158
|
+
var _existingWallet_externalServerKeyShares1;
|
|
864
1159
|
const { shares } = this.recoverStrategy({
|
|
865
1160
|
externalServerKeySharesBackupInfo: existingWallet.externalServerKeySharesBackupInfo || {
|
|
866
1161
|
backups: getExternalServerKeyShareBackupInfo()
|
|
@@ -870,11 +1165,35 @@ class DynamicWalletClient {
|
|
|
870
1165
|
shareCount
|
|
871
1166
|
});
|
|
872
1167
|
const { dynamic: requiredDynamicKeyShareIds = [] } = shares;
|
|
873
|
-
|
|
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) {
|
|
874
1183
|
keyshareCheck = true;
|
|
875
1184
|
}
|
|
876
1185
|
}
|
|
877
|
-
|
|
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;
|
|
878
1197
|
}
|
|
879
1198
|
/**
|
|
880
1199
|
* verifyPassword attempts to recover and decrypt a single client key share using the provided password.
|
|
@@ -900,8 +1219,10 @@ class DynamicWalletClient {
|
|
|
900
1219
|
accountAddress
|
|
901
1220
|
});
|
|
902
1221
|
const { dynamic: dynamicKeyShareIds = [] } = backups;
|
|
903
|
-
|
|
904
|
-
|
|
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');
|
|
905
1226
|
}
|
|
906
1227
|
try {
|
|
907
1228
|
await this.recoverEncryptedBackupByWallet({
|
|
@@ -946,88 +1267,164 @@ class DynamicWalletClient {
|
|
|
946
1267
|
if (walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.RESHARE) {
|
|
947
1268
|
return true;
|
|
948
1269
|
}
|
|
949
|
-
const { requiredShareCount } = this.recoverStrategy({
|
|
1270
|
+
const { shares, requiredShareCount } = this.recoverStrategy({
|
|
950
1271
|
externalServerKeySharesBackupInfo,
|
|
951
1272
|
thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme,
|
|
952
1273
|
walletOperation
|
|
953
1274
|
});
|
|
954
|
-
|
|
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) {
|
|
955
1280
|
return false;
|
|
956
1281
|
}
|
|
957
1282
|
return true;
|
|
958
1283
|
}
|
|
959
1284
|
async getWalletExternalServerKeyShareBackupInfo({ accountAddress }) {
|
|
960
|
-
var
|
|
1285
|
+
var _wallet_externalServerKeySharesBackupInfo_backups_BackupLocation_DYNAMIC, _wallet_externalServerKeySharesBackupInfo_backups, _wallet_externalServerKeySharesBackupInfo, _user_verifiedCredentials;
|
|
961
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
|
+
}
|
|
962
1295
|
// Return existing backup info if it exists
|
|
963
|
-
if (((
|
|
964
|
-
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;
|
|
965
1298
|
}
|
|
966
1299
|
// Get backup info from server
|
|
967
|
-
const user = await this.apiClient.getUser();
|
|
968
|
-
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());
|
|
969
1302
|
return getExternalServerKeyShareBackupInfo({
|
|
970
|
-
walletProperties:
|
|
1303
|
+
walletProperties: walletData == null ? void 0 : walletData.walletProperties
|
|
971
1304
|
});
|
|
972
1305
|
}
|
|
973
1306
|
async getWallet({ accountAddress, walletOperation = WalletOperation.NO_OPERATION, shareCount = undefined, password = undefined }) {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
accountAddress,
|
|
978
|
-
walletOperation,
|
|
979
|
-
shareCount
|
|
980
|
-
});
|
|
981
|
-
if (existingWalletCheck) {
|
|
982
|
-
this.logger.debug(`Wallet ${accountAddress} already exists`);
|
|
983
|
-
return this.walletMap[accountAddress];
|
|
984
|
-
}
|
|
985
|
-
//todo: Question - why don't we just call getWallets here? so then all are preloaded
|
|
986
|
-
// Fetch and restore wallet from server
|
|
987
|
-
const user = await this.apiClient.getUser();
|
|
988
|
-
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
989
|
-
this.logger.debug('Restoring wallet', wallet);
|
|
990
|
-
const walletProperties = wallet.walletProperties;
|
|
991
|
-
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
992
|
-
walletId: wallet.id,
|
|
993
|
-
chainName: wallet.chainName,
|
|
994
|
-
accountAddress,
|
|
995
|
-
thresholdSignatureScheme: walletProperties.thresholdSignatureScheme,
|
|
996
|
-
derivationPath: walletProperties.derivationPath,
|
|
997
|
-
externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo({
|
|
998
|
-
walletProperties
|
|
999
|
-
})
|
|
1000
|
-
});
|
|
1001
|
-
if (walletOperation !== WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
|
|
1002
|
-
accountAddress,
|
|
1003
|
-
walletOperation
|
|
1004
|
-
})) {
|
|
1005
|
-
const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
|
|
1307
|
+
try {
|
|
1308
|
+
this.ensureApiClientAuthenticated();
|
|
1309
|
+
const existingWalletCheck = await this.checkWalletFields({
|
|
1006
1310
|
accountAddress,
|
|
1007
|
-
|
|
1008
|
-
walletOperation: walletOperation,
|
|
1311
|
+
walletOperation,
|
|
1009
1312
|
shareCount
|
|
1010
1313
|
});
|
|
1011
|
-
|
|
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;
|
|
1012
1350
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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];
|
|
1016
1359
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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;
|
|
1020
1415
|
}
|
|
1021
|
-
return this.walletMap[accountAddress];
|
|
1022
1416
|
}
|
|
1023
|
-
|
|
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() {
|
|
1024
1421
|
var _user_verifiedCredentials;
|
|
1025
1422
|
this.ensureApiClientAuthenticated();
|
|
1026
|
-
const user = await this.apiClient.getUser();
|
|
1423
|
+
const user = await this.apiClient.getUser(v4());
|
|
1027
1424
|
const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
|
|
1028
1425
|
const wallets = waasWallets.map((vc)=>{
|
|
1029
|
-
var _this_walletMap_vc_address,
|
|
1030
|
-
var
|
|
1426
|
+
var _this_walletMap_vc_address, _vc_walletProperties, _vc_walletProperties1;
|
|
1427
|
+
var _vc_walletProperties_derivationPath;
|
|
1031
1428
|
return {
|
|
1032
1429
|
walletId: vc.id,
|
|
1033
1430
|
chainName: vc.chain,
|
|
@@ -1036,37 +1433,223 @@ class DynamicWalletClient {
|
|
|
1036
1433
|
walletProperties: vc.walletProperties || {}
|
|
1037
1434
|
}),
|
|
1038
1435
|
externalServerKeyShares: ((_this_walletMap_vc_address = this.walletMap[vc.address]) == null ? void 0 : _this_walletMap_vc_address.externalServerKeyShares) || [],
|
|
1039
|
-
derivationPath: (
|
|
1040
|
-
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
|
|
1041
1438
|
};
|
|
1042
1439
|
});
|
|
1043
1440
|
this.walletMap = wallets.reduce((acc, wallet)=>{
|
|
1044
|
-
var _acc_accountAddress;
|
|
1045
|
-
const accountAddress = wallet.accountAddress;
|
|
1046
1441
|
acc[wallet.accountAddress] = {
|
|
1047
1442
|
walletId: wallet.walletId,
|
|
1048
1443
|
chainName: wallet.chainName,
|
|
1049
1444
|
accountAddress: wallet.accountAddress,
|
|
1050
1445
|
externalServerKeyShares: wallet.externalServerKeyShares || [],
|
|
1051
1446
|
externalServerKeySharesBackupInfo: wallet.externalServerKeySharesBackupInfo,
|
|
1052
|
-
derivationPath:
|
|
1447
|
+
derivationPath: wallet.derivationPath,
|
|
1053
1448
|
thresholdSignatureScheme: wallet.thresholdSignatureScheme
|
|
1054
1449
|
};
|
|
1055
1450
|
return acc;
|
|
1056
1451
|
}, {});
|
|
1057
1452
|
return wallets;
|
|
1058
1453
|
}
|
|
1059
|
-
constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, debug }){
|
|
1454
|
+
constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, debug, forwardMPCClient, enableMPCAccelerator = false }){
|
|
1060
1455
|
this.logger = logger;
|
|
1061
1456
|
this.walletMap = {} // todo: store in session storage
|
|
1062
1457
|
;
|
|
1063
1458
|
this.isApiClientAuthenticated = false;
|
|
1459
|
+
this.forwardMPCEnabled = false;
|
|
1064
1460
|
this.environmentId = environmentId;
|
|
1065
1461
|
this.baseMPCRelayApiUrl = baseMPCRelayApiUrl;
|
|
1066
1462
|
this.baseApiUrl = baseApiUrl;
|
|
1067
1463
|
this.debug = Boolean(debug);
|
|
1068
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
|
+
}
|
|
1069
1476
|
}
|
|
1070
1477
|
}
|
|
1071
1478
|
|
|
1072
|
-
|
|
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 };
|