@dynamic-labs-wallet/node 0.0.354 → 1.0.0-beta
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 +443 -429
- package/index.esm.js +444 -431
- package/package.json +3 -3
- package/src/client.d.ts +164 -78
- package/src/client.d.ts.map +1 -1
- package/src/index.d.ts +2 -2
- package/src/index.d.ts.map +1 -1
- package/src/types.d.ts +35 -1
- package/src/types.d.ts.map +1 -1
package/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from '#internal/core';
|
|
2
|
-
import { MPC_RELAY_PROD_API_URL, getMPCChainConfig, BackupLocation, isAxiosError, handleAxiosError, Logger, DynamicApiClient, getClientThreshold, MPC_CONFIG, classifyForwardMpcError, getTSSConfig, serializeMessageForForwardMPC, getEnvironmentFromUrl, MPC_RELAY_URL_MAP, WalletOperation, getServerWalletReshareConfig, getRequiredExternalKeyShareId,
|
|
2
|
+
import { MPC_RELAY_PROD_API_URL, getMPCChainConfig, BackupLocation, isAxiosError, handleAxiosError, Logger, DynamicApiClient, getClientThreshold, MPC_CONFIG, classifyForwardMpcError, getTSSConfig, serializeMessageForForwardMPC, getEnvironmentFromUrl, MPC_RELAY_URL_MAP, WalletOperation, getServerWalletReshareConfig, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, ThresholdSignatureScheme, DYNAMIC_AUTH_PROD_BASE_API_URL, DYNAMIC_FORWARD_MPC_ENCLAVE_URL_MAP, DYNAMIC_FORWARD_MPC_ENCLAVE_ATTESTATION_CONFIG_MAP } from '@dynamic-labs-wallet/core';
|
|
3
3
|
export { SOLANA_RPC_URL, ThresholdSignatureScheme, WalletOperation, getMPCChainConfig } from '@dynamic-labs-wallet/core';
|
|
4
4
|
import { SigningAlgorithm } from '@dynamic-labs-wallet/primitives';
|
|
5
5
|
import { BIP340, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, BIP340KeygenResult } from '#internal/node';
|
|
@@ -7,7 +7,7 @@ import { Logger as Logger$1 } from '@dynamic-labs/logger';
|
|
|
7
7
|
import { ForwardMPCClientV2 } from '@dynamic-labs-wallet/forward-mpc-client';
|
|
8
8
|
import { v4 } from 'uuid';
|
|
9
9
|
import crypto from 'crypto';
|
|
10
|
-
import 'node:crypto';
|
|
10
|
+
import crypto$1 from 'node:crypto';
|
|
11
11
|
|
|
12
12
|
// Removed duplicate exports - these are already exported from #internal/core
|
|
13
13
|
const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD_API_URL })=>{
|
|
@@ -316,6 +316,33 @@ class DynamicWalletClient {
|
|
|
316
316
|
throw new Error('Client must be authenticated before making API calls. Call authenticateApiToken first.');
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Guards against silent operate-on-different-wallet bugs when callers pass
|
|
321
|
+
* both `accountAddress` and `walletMetadata`. Throws when either is missing
|
|
322
|
+
* or when they disagree.
|
|
323
|
+
*
|
|
324
|
+
* Comparison is case-insensitive only for EVM (where checksum casing is
|
|
325
|
+
* decoration over an underlying hex address). SVM (base58), BTC base58,
|
|
326
|
+
* and TON (base64url) are case-sensitive — two addresses that differ only
|
|
327
|
+
* in case are different addresses.
|
|
328
|
+
*/ assertAddressMatchesMetadata(accountAddress, walletMetadata) {
|
|
329
|
+
var _walletMetadata_chainName;
|
|
330
|
+
if (!accountAddress) {
|
|
331
|
+
throw new Error('accountAddress is required and must not be empty.');
|
|
332
|
+
}
|
|
333
|
+
if (!walletMetadata.accountAddress) {
|
|
334
|
+
throw new Error('walletMetadata.accountAddress is required and must not be empty.');
|
|
335
|
+
}
|
|
336
|
+
// chainName comparison is case-insensitive — chain clients set the
|
|
337
|
+
// canonical uppercase form, but server-fetched walletMetadata could
|
|
338
|
+
// theoretically arrive with a different case.
|
|
339
|
+
const isEvm = ((_walletMetadata_chainName = walletMetadata.chainName) == null ? void 0 : _walletMetadata_chainName.toUpperCase()) === 'EVM';
|
|
340
|
+
const left = isEvm ? accountAddress.toLowerCase() : accountAddress;
|
|
341
|
+
const right = isEvm ? walletMetadata.accountAddress.toLowerCase() : walletMetadata.accountAddress;
|
|
342
|
+
if (left !== right) {
|
|
343
|
+
throw new Error('accountAddress mismatch: parameter does not match walletMetadata.accountAddress. ' + 'Pass them consistently — preferably read from walletMetadata.accountAddress.');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
319
346
|
async authenticateApiToken(authToken) {
|
|
320
347
|
const tmpClient = new DynamicApiClient({
|
|
321
348
|
environmentId: this.environmentId,
|
|
@@ -337,6 +364,57 @@ class DynamicWalletClient {
|
|
|
337
364
|
});
|
|
338
365
|
this.isApiClientAuthenticated = true;
|
|
339
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Fetches non-sensitive wallet identity (walletId, accountAddress, chainName,
|
|
369
|
+
* derivationPath, thresholdSignatureScheme) from the Dynamic API by
|
|
370
|
+
* `accountAddress`. SDK-scoped — works with the customer auth token from
|
|
371
|
+
* `authenticateApiToken`.
|
|
372
|
+
*
|
|
373
|
+
* **What this returns and what it does NOT return:**
|
|
374
|
+
* - Returns: wallet identity fields above.
|
|
375
|
+
* - Does NOT return: `externalServerKeySharesBackupInfo` (the per-share
|
|
376
|
+
* pointer metadata that drives recovery), `addressType`, or any
|
|
377
|
+
* encrypted material. The byAddress endpoint is intentionally a slim
|
|
378
|
+
* identity lookup; pointer metadata and addressType only come from the
|
|
379
|
+
* `createWalletAccount` / `importPrivateKey` return values.
|
|
380
|
+
*
|
|
381
|
+
* **Customer responsibility:**
|
|
382
|
+
* Persist the FULL `walletMetadata` returned by `createWalletAccount` /
|
|
383
|
+
* `importPrivateKey` (which includes `externalServerKeySharesBackupInfo` and
|
|
384
|
+
* `addressType`). `fetchWalletMetadata` recovers identity only — it cannot
|
|
385
|
+
* substitute for a properly-cached `walletMetadata` after a Redis wipe if
|
|
386
|
+
* the operation needs `backupInfo` (sign with auto-recovery, password
|
|
387
|
+
* checks, refresh, etc.).
|
|
388
|
+
*
|
|
389
|
+
* Does not cache, does not mutate SDK state.
|
|
390
|
+
*/ async fetchWalletMetadata(accountAddress) {
|
|
391
|
+
this.ensureApiClientAuthenticated();
|
|
392
|
+
const { wallet } = await this.apiClient.getWaasWalletByAddress({
|
|
393
|
+
walletAddress: accountAddress
|
|
394
|
+
});
|
|
395
|
+
return {
|
|
396
|
+
walletId: wallet.walletId,
|
|
397
|
+
accountAddress: wallet.accountAddress,
|
|
398
|
+
chainName: wallet.chainName,
|
|
399
|
+
thresholdSignatureScheme: wallet.thresholdSignatureScheme,
|
|
400
|
+
derivationPath: wallet.derivationPath
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Returns the cached `externalServerKeySharesBackupInfo` from
|
|
405
|
+
* `walletMetadata`. The Dynamic API does not currently expose a SDK-scoped
|
|
406
|
+
* endpoint that returns the keyShare pointer metadata by address (only the
|
|
407
|
+
* unbounded `getUser` does, which we avoid for server wallets). So the
|
|
408
|
+
* customer must persist the full `walletMetadata` (with backupInfo)
|
|
409
|
+
* returned by `createWalletAccount` / `importPrivateKey` and pass it back
|
|
410
|
+
* in on subsequent calls. Throws loudly when missing — never silently
|
|
411
|
+
* synthesizes empty state.
|
|
412
|
+
*/ async resolveBackupInfo(walletMetadata) {
|
|
413
|
+
if (!walletMetadata.externalServerKeySharesBackupInfo) {
|
|
414
|
+
throw new Error('walletMetadata.externalServerKeySharesBackupInfo is required. ' + 'Persist the full walletMetadata returned by createWalletAccount/importPrivateKey ' + '(including externalServerKeySharesBackupInfo) and pass it back in on subsequent calls.');
|
|
415
|
+
}
|
|
416
|
+
return walletMetadata.externalServerKeySharesBackupInfo;
|
|
417
|
+
}
|
|
340
418
|
async dynamicServerInitializeKeyGen({ chainName, externalServerKeygenIds, thresholdSignatureScheme, dynamicRequestId, skipLock, bitcoinConfig, onError, onCeremonyComplete }) {
|
|
341
419
|
this.ensureApiClientAuthenticated();
|
|
342
420
|
try {
|
|
@@ -513,7 +591,16 @@ class DynamicWalletClient {
|
|
|
513
591
|
throw new Error('Error deriving public key in externalServerKeyGen');
|
|
514
592
|
}
|
|
515
593
|
}
|
|
516
|
-
|
|
594
|
+
validatePasswordForBackup({ password, backUpToDynamic }) {
|
|
595
|
+
if (backUpToDynamic && (!password || password === this.environmentId)) {
|
|
596
|
+
throw new Error('A password is required when backing up to Dynamic. ' + 'You can use the same password for all wallets or a unique password per wallet.');
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
async keyGen({ chainName, thresholdSignatureScheme, skipLock, bitcoinConfig, password, backUpToDynamic, onError, onCeremonyComplete }) {
|
|
600
|
+
this.validatePasswordForBackup({
|
|
601
|
+
password,
|
|
602
|
+
backUpToDynamic
|
|
603
|
+
});
|
|
517
604
|
const dynamicRequestId = v4();
|
|
518
605
|
try {
|
|
519
606
|
const externalServerInitKeygenResults = await this.externalServerInitializeKeyGen({
|
|
@@ -548,7 +635,11 @@ class DynamicWalletClient {
|
|
|
548
635
|
throw new Error('Error creating wallet account in keyGen');
|
|
549
636
|
}
|
|
550
637
|
}
|
|
551
|
-
async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, onError, onCeremonyComplete }) {
|
|
638
|
+
async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, password, backUpToDynamic, onError, onCeremonyComplete }) {
|
|
639
|
+
this.validatePasswordForBackup({
|
|
640
|
+
password,
|
|
641
|
+
backUpToDynamic
|
|
642
|
+
});
|
|
552
643
|
this.ensureApiClientAuthenticated();
|
|
553
644
|
const dynamicRequestId = v4();
|
|
554
645
|
const mpcSigner = getMPCSigner({
|
|
@@ -648,9 +739,13 @@ class DynamicWalletClient {
|
|
|
648
739
|
this.logger.debug('Signing message with MPC Socket Client');
|
|
649
740
|
const environment = getEnvironmentFromUrl(this.baseApiUrl);
|
|
650
741
|
const defaultRelayUrl = MPC_RELAY_URL_MAP[environment];
|
|
742
|
+
// Ed25519 chains (SVM, TON) must send the formatted (hex) message — the
|
|
743
|
+
// forward-MPC server decodes it as hex before signing. Raw plaintext for
|
|
744
|
+
// odd-length messages produces "hex string expected, got unpadded hex of length N".
|
|
745
|
+
const isEd25519Chain = chainName === 'SVM' || chainName === 'TON';
|
|
651
746
|
const { signature: signatureBytes } = await this.apiClient.forwardMPCClient.signMessage({
|
|
652
747
|
keyshare: keyShare,
|
|
653
|
-
message:
|
|
748
|
+
message: isEd25519Chain ? formattedMessage : messageForForwardMPC,
|
|
654
749
|
relayDomain: this.baseMPCRelayApiUrl || defaultRelayUrl,
|
|
655
750
|
signingAlgo: chainName === 'EVM' ? 'ECDSA' : 'ED25519',
|
|
656
751
|
hashAlgo: chainName === 'EVM' && !isFormatted ? 'keccak256' : undefined,
|
|
@@ -718,48 +813,59 @@ class DynamicWalletClient {
|
|
|
718
813
|
return undefined;
|
|
719
814
|
}
|
|
720
815
|
}
|
|
721
|
-
async sign({ accountAddress, externalServerKeyShares, message, chainName, password = undefined, isFormatted = false, context, onError, bitcoinConfig }) {
|
|
816
|
+
async sign({ accountAddress, externalServerKeyShares, message, chainName, password = undefined, isFormatted = false, context, onError, bitcoinConfig, walletMetadata, walletOperation = WalletOperation.SIGN_MESSAGE, passwordPreVerified = false }) {
|
|
722
817
|
try {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const
|
|
818
|
+
this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
|
|
819
|
+
const backupInfo = walletMetadata.externalServerKeySharesBackupInfo;
|
|
820
|
+
// Recover internally if no shares supplied — decryption with `password`
|
|
821
|
+
// implicitly validates it, so we won't re-run verifyPassword below.
|
|
822
|
+
const resolvedShares = externalServerKeyShares && externalServerKeyShares.length > 0 ? externalServerKeyShares : await this.resolveKeyShares({
|
|
728
823
|
accountAddress,
|
|
729
|
-
|
|
730
|
-
|
|
824
|
+
password,
|
|
825
|
+
walletOperation,
|
|
826
|
+
externalServerKeyShares,
|
|
827
|
+
errorMessage: 'External server key shares are required to sign a message',
|
|
828
|
+
walletMetadata,
|
|
829
|
+
backupInfo
|
|
731
830
|
});
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
this.
|
|
831
|
+
const sharesProvided = resolvedShares === externalServerKeyShares;
|
|
832
|
+
// verifyPassword only when shares came from the caller (no implicit
|
|
833
|
+
// validation). BTC sets `passwordPreVerified` when it pre-resolved.
|
|
834
|
+
// Refuse to silently sign without verifying — missing backupInfo on
|
|
835
|
+
// this branch (e.g. caller built walletMetadata via fetchWalletMetadata
|
|
836
|
+
// identity-only path) would otherwise let any password through.
|
|
837
|
+
if (sharesProvided && !passwordPreVerified) {
|
|
838
|
+
if (!backupInfo) {
|
|
839
|
+
throw new Error('walletMetadata.externalServerKeySharesBackupInfo is required to verify the password ' + 'when signing with caller-supplied externalServerKeyShares. Persist the full walletMetadata ' + 'returned by createWalletAccount/importPrivateKey and pass it back in. ' + 'fetchWalletMetadata returns identity-only metadata and cannot be used on this path.');
|
|
741
840
|
}
|
|
742
|
-
|
|
841
|
+
await this.verifyPassword({
|
|
842
|
+
accountAddress,
|
|
843
|
+
password,
|
|
844
|
+
walletMetadata,
|
|
845
|
+
backupInfo
|
|
846
|
+
});
|
|
743
847
|
}
|
|
848
|
+
const walletId = walletMetadata.walletId;
|
|
849
|
+
const derivationPathStr = walletMetadata.derivationPath;
|
|
744
850
|
// Perform the dynamic server sign
|
|
745
851
|
const data = await this.dynamicServerSign({
|
|
746
|
-
walletId
|
|
852
|
+
walletId,
|
|
747
853
|
message,
|
|
748
854
|
isFormatted,
|
|
749
855
|
context,
|
|
750
856
|
bitcoinConfig,
|
|
751
857
|
onError
|
|
752
858
|
});
|
|
753
|
-
const derivationPath =
|
|
859
|
+
const derivationPath = derivationPathStr && derivationPathStr != '' ? new Uint32Array(Object.values(JSON.parse(derivationPathStr))) : undefined;
|
|
754
860
|
// Try forward MPC first if enabled, with fallback to relay-based signing on attestation failure
|
|
755
861
|
const forwardMpcSignature = await this.trySignViaForwardMpc({
|
|
756
862
|
chainName,
|
|
757
863
|
message,
|
|
758
864
|
roomId: data.roomId,
|
|
759
|
-
keyShare:
|
|
865
|
+
keyShare: resolvedShares[0],
|
|
760
866
|
derivationPath,
|
|
761
867
|
isFormatted,
|
|
762
|
-
walletId
|
|
868
|
+
walletId,
|
|
763
869
|
dynamicRequestId: data.dynamicRequestId
|
|
764
870
|
});
|
|
765
871
|
if (forwardMpcSignature !== undefined) {
|
|
@@ -770,7 +876,7 @@ class DynamicWalletClient {
|
|
|
770
876
|
chainName,
|
|
771
877
|
message,
|
|
772
878
|
roomId: data.roomId,
|
|
773
|
-
keyShare:
|
|
879
|
+
keyShare: resolvedShares[0],
|
|
774
880
|
derivationPath,
|
|
775
881
|
isFormatted,
|
|
776
882
|
bitcoinConfig
|
|
@@ -782,41 +888,47 @@ class DynamicWalletClient {
|
|
|
782
888
|
throw error;
|
|
783
889
|
}
|
|
784
890
|
}
|
|
785
|
-
async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, externalServerKeyShares,
|
|
891
|
+
async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, externalServerKeyShares, backUpToDynamic = false, walletMetadata }) {
|
|
892
|
+
this.validatePasswordForBackup({
|
|
893
|
+
password,
|
|
894
|
+
backUpToDynamic
|
|
895
|
+
});
|
|
786
896
|
this.ensureApiClientAuthenticated();
|
|
897
|
+
this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
|
|
898
|
+
const backupInfo = await this.resolveBackupInfo(walletMetadata);
|
|
787
899
|
await this.verifyPassword({
|
|
788
900
|
accountAddress,
|
|
789
|
-
password
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
accountAddress,
|
|
793
|
-
walletOperation: WalletOperation.REFRESH,
|
|
794
|
-
password
|
|
901
|
+
password,
|
|
902
|
+
walletMetadata,
|
|
903
|
+
backupInfo
|
|
795
904
|
});
|
|
905
|
+
const bitcoinConfig = chainName === 'BTC' && walletMetadata.addressType ? {
|
|
906
|
+
addressType: walletMetadata.addressType
|
|
907
|
+
} : undefined;
|
|
796
908
|
const mpcSigner = getMPCSigner({
|
|
797
909
|
chainName,
|
|
798
|
-
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
910
|
+
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
911
|
+
bitcoinConfig
|
|
799
912
|
});
|
|
800
|
-
// Create the room and refresh the shares
|
|
801
913
|
const data = await this.apiClient.refreshWalletAccountShares({
|
|
802
|
-
walletId:
|
|
914
|
+
walletId: walletMetadata.walletId
|
|
803
915
|
});
|
|
804
916
|
const roomId = data.roomId;
|
|
805
|
-
externalServerKeyShares = externalServerKeyShares != null ? externalServerKeyShares : wallet.externalServerKeyShares;
|
|
806
917
|
if (!externalServerKeyShares) {
|
|
807
918
|
throw new Error('External server key shares are required to refresh');
|
|
808
919
|
}
|
|
809
920
|
const refreshResults = await Promise.all(externalServerKeyShares.map((serverKeyShare)=>mpcSigner.refresh(roomId, serverKeyShare)));
|
|
810
|
-
|
|
811
|
-
externalServerKeyShares: refreshResults,
|
|
812
|
-
externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo()
|
|
813
|
-
});
|
|
814
|
-
await this.storeEncryptedBackupByWallet({
|
|
921
|
+
const { backupInfo: updatedBackupInfo } = await this.storeEncryptedBackupByWallet({
|
|
815
922
|
accountAddress,
|
|
816
923
|
password: password != null ? password : this.environmentId,
|
|
817
|
-
|
|
924
|
+
backUpToDynamic,
|
|
925
|
+
externalServerKeyShares: refreshResults,
|
|
926
|
+
walletMetadata
|
|
818
927
|
});
|
|
819
|
-
return
|
|
928
|
+
return {
|
|
929
|
+
externalServerKeyShares: refreshResults,
|
|
930
|
+
backupInfo: updatedBackupInfo
|
|
931
|
+
};
|
|
820
932
|
}
|
|
821
933
|
async getExportId({ chainName, serverKeyShare, bitcoinConfig }) {
|
|
822
934
|
const mpcSigner = getMPCSigner({
|
|
@@ -874,31 +986,29 @@ class DynamicWalletClient {
|
|
|
874
986
|
existingExternalServerKeyShares
|
|
875
987
|
};
|
|
876
988
|
}
|
|
877
|
-
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, externalServerKeyShares,
|
|
989
|
+
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, externalServerKeyShares, backUpToDynamic = false, walletMetadata }) {
|
|
990
|
+
this.validatePasswordForBackup({
|
|
991
|
+
password,
|
|
992
|
+
backUpToDynamic
|
|
993
|
+
});
|
|
878
994
|
this.ensureApiClientAuthenticated();
|
|
995
|
+
this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
|
|
996
|
+
const backupInfo = await this.resolveBackupInfo(walletMetadata);
|
|
879
997
|
await this.verifyPassword({
|
|
880
998
|
accountAddress,
|
|
881
|
-
password
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
oldThresholdSignatureScheme,
|
|
885
|
-
newThresholdSignatureScheme
|
|
886
|
-
});
|
|
887
|
-
const wallet = await this.getWallet({
|
|
888
|
-
accountAddress,
|
|
889
|
-
walletOperation: WalletOperation.RESHARE,
|
|
890
|
-
shareCount: existingExternalServerShareCount,
|
|
891
|
-
password
|
|
999
|
+
password,
|
|
1000
|
+
walletMetadata,
|
|
1001
|
+
backupInfo
|
|
892
1002
|
});
|
|
893
|
-
externalServerKeyShares = externalServerKeyShares != null ? externalServerKeyShares : wallet.externalServerKeyShares;
|
|
894
1003
|
if (!externalServerKeyShares) {
|
|
895
1004
|
throw new Error('External server key shares are required to reshare');
|
|
896
1005
|
}
|
|
897
1006
|
const { newExternalServerInitKeygenResults, newExternalServerKeygenIds, existingExternalServerKeygenIds, existingExternalServerKeyShares } = await this.reshareStrategy({
|
|
898
1007
|
chainName,
|
|
899
|
-
wallet:
|
|
1008
|
+
wallet: {
|
|
1009
|
+
addressType: walletMetadata.addressType,
|
|
900
1010
|
externalServerKeyShares
|
|
901
|
-
}
|
|
1011
|
+
},
|
|
902
1012
|
oldThresholdSignatureScheme,
|
|
903
1013
|
newThresholdSignatureScheme
|
|
904
1014
|
});
|
|
@@ -906,15 +1016,13 @@ class DynamicWalletClient {
|
|
|
906
1016
|
...newExternalServerKeygenIds,
|
|
907
1017
|
...existingExternalServerKeygenIds
|
|
908
1018
|
];
|
|
909
|
-
// Server to create the room and complete the server reshare logics
|
|
910
1019
|
const data = await this.apiClient.reshare({
|
|
911
|
-
walletId:
|
|
1020
|
+
walletId: walletMetadata.walletId,
|
|
912
1021
|
clientKeygenIds: externalServerKeygenIds,
|
|
913
1022
|
oldThresholdSignatureScheme,
|
|
914
1023
|
newThresholdSignatureScheme
|
|
915
1024
|
});
|
|
916
1025
|
const { roomId, serverKeygenIds: dynamicServerKeygenIds, newServerKeygenIds: newDynamicServerKeygenIds = [] } = data;
|
|
917
|
-
// Get the MPC config for the threshold signature scheme
|
|
918
1026
|
const oldMpcConfig = MPC_CONFIG[oldThresholdSignatureScheme];
|
|
919
1027
|
const newMpcConfig = MPC_CONFIG[newThresholdSignatureScheme];
|
|
920
1028
|
const allPartyKeygenIds = [
|
|
@@ -922,40 +1030,60 @@ class DynamicWalletClient {
|
|
|
922
1030
|
...dynamicServerKeygenIds,
|
|
923
1031
|
...newDynamicServerKeygenIds
|
|
924
1032
|
];
|
|
1033
|
+
const bitcoinConfig = chainName === 'BTC' && walletMetadata.addressType ? {
|
|
1034
|
+
addressType: walletMetadata.addressType
|
|
1035
|
+
} : undefined;
|
|
925
1036
|
const mpcSigner = getMPCSigner({
|
|
926
1037
|
chainName,
|
|
927
|
-
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1038
|
+
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
1039
|
+
bitcoinConfig
|
|
928
1040
|
});
|
|
929
1041
|
const reshareResults = await Promise.all([
|
|
930
1042
|
...newExternalServerInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds)),
|
|
931
1043
|
...existingExternalServerKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds))
|
|
932
1044
|
]);
|
|
933
|
-
|
|
934
|
-
externalServerKeyShares: reshareResults,
|
|
935
|
-
externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo()
|
|
936
|
-
});
|
|
937
|
-
await this.storeEncryptedBackupByWallet({
|
|
1045
|
+
const { backupInfo: updatedBackupInfo } = await this.storeEncryptedBackupByWallet({
|
|
938
1046
|
accountAddress,
|
|
939
1047
|
password,
|
|
940
|
-
|
|
1048
|
+
backUpToDynamic,
|
|
1049
|
+
externalServerKeyShares: reshareResults,
|
|
1050
|
+
walletMetadata
|
|
941
1051
|
});
|
|
942
|
-
return
|
|
1052
|
+
return {
|
|
1053
|
+
externalServerKeyShares: reshareResults,
|
|
1054
|
+
backupInfo: updatedBackupInfo
|
|
1055
|
+
};
|
|
943
1056
|
}
|
|
944
|
-
async exportKey({ accountAddress, chainName, password = undefined, externalServerKeyShares, bitcoinConfig, elevatedAccessToken }) {
|
|
1057
|
+
async exportKey({ accountAddress, chainName, password = undefined, externalServerKeyShares, bitcoinConfig, elevatedAccessToken, walletMetadata }) {
|
|
945
1058
|
this.ensureApiClientAuthenticated();
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
});
|
|
950
|
-
const wallet = await this.getWallet({
|
|
1059
|
+
this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
|
|
1060
|
+
const backupInfo = walletMetadata.externalServerKeySharesBackupInfo;
|
|
1061
|
+
const resolvedShares = externalServerKeyShares && externalServerKeyShares.length > 0 ? externalServerKeyShares : await this.resolveKeyShares({
|
|
951
1062
|
accountAddress,
|
|
952
1063
|
password,
|
|
953
|
-
walletOperation: WalletOperation.EXPORT_PRIVATE_KEY
|
|
1064
|
+
walletOperation: WalletOperation.EXPORT_PRIVATE_KEY,
|
|
1065
|
+
externalServerKeyShares,
|
|
1066
|
+
errorMessage: 'External server key shares are required to export a private key',
|
|
1067
|
+
walletMetadata,
|
|
1068
|
+
backupInfo
|
|
954
1069
|
});
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1070
|
+
const sharesProvided = resolvedShares === externalServerKeyShares;
|
|
1071
|
+
// Refuse to export without verifying — exportKey returns the raw private
|
|
1072
|
+
// key, so a missing backupInfo on the caller-supplied branch would let
|
|
1073
|
+
// any password through. See sign() for the analogous guard.
|
|
1074
|
+
if (sharesProvided) {
|
|
1075
|
+
if (!backupInfo) {
|
|
1076
|
+
throw new Error('walletMetadata.externalServerKeySharesBackupInfo is required to verify the password ' + 'when exporting with caller-supplied externalServerKeyShares. Persist the full walletMetadata ' + 'returned by createWalletAccount/importPrivateKey and pass it back in. ' + 'fetchWalletMetadata returns identity-only metadata and cannot be used on this path.');
|
|
1077
|
+
}
|
|
1078
|
+
await this.verifyPassword({
|
|
1079
|
+
accountAddress,
|
|
1080
|
+
password,
|
|
1081
|
+
walletMetadata,
|
|
1082
|
+
backupInfo
|
|
1083
|
+
});
|
|
958
1084
|
}
|
|
1085
|
+
const walletId = walletMetadata.walletId;
|
|
1086
|
+
const derivationPathStr = walletMetadata.derivationPath;
|
|
959
1087
|
const mpcSigner = getMPCSigner({
|
|
960
1088
|
chainName,
|
|
961
1089
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
@@ -963,20 +1091,20 @@ class DynamicWalletClient {
|
|
|
963
1091
|
});
|
|
964
1092
|
const exportId = await this.getExportId({
|
|
965
1093
|
chainName,
|
|
966
|
-
serverKeyShare:
|
|
1094
|
+
serverKeyShare: resolvedShares[0],
|
|
967
1095
|
bitcoinConfig
|
|
968
1096
|
});
|
|
969
1097
|
const data = await this.apiClient.exportKey({
|
|
970
|
-
walletId
|
|
1098
|
+
walletId,
|
|
971
1099
|
exportId,
|
|
972
1100
|
bitcoinConfig,
|
|
973
1101
|
elevatedAccessToken
|
|
974
1102
|
});
|
|
975
|
-
const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId,
|
|
1103
|
+
const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId, resolvedShares[0], exportId);
|
|
976
1104
|
if (!keyExportRaw) {
|
|
977
1105
|
throw new Error('Error exporting private key');
|
|
978
1106
|
}
|
|
979
|
-
const derivationPath =
|
|
1107
|
+
const derivationPath = derivationPathStr && derivationPathStr != '' ? new Uint32Array(Object.values(JSON.parse(derivationPathStr))) : undefined;
|
|
980
1108
|
let derivedPrivateKey;
|
|
981
1109
|
if (mpcSigner instanceof Ecdsa) {
|
|
982
1110
|
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
|
|
@@ -1033,53 +1161,36 @@ class DynamicWalletClient {
|
|
|
1033
1161
|
const serializedEncryptedKeyShare = Buffer.from(JSON.stringify(encryptedKeyShare)).toString('base64');
|
|
1034
1162
|
return serializedEncryptedKeyShare;
|
|
1035
1163
|
}
|
|
1036
|
-
async
|
|
1037
|
-
let retries = 0;
|
|
1038
|
-
const maxRetries = 3;
|
|
1039
|
-
while((!this.walletMap[accountAddress] || !this.walletMap[accountAddress].walletId) && retries < maxRetries){
|
|
1040
|
-
await new Promise((resolve)=>setTimeout(resolve, 1000)); // Wait 1 second
|
|
1041
|
-
retries++;
|
|
1042
|
-
}
|
|
1043
|
-
if (!this.walletMap[accountAddress] || !this.walletMap[accountAddress].walletId) {
|
|
1044
|
-
throw new Error('Ceremony completion timeout');
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
async storeEncryptedBackupByWallet({ accountAddress, externalServerKeyShares = undefined, password = undefined, backUpToClientShareService }) {
|
|
1164
|
+
async storeEncryptedBackupByWallet({ accountAddress, externalServerKeyShares = undefined, password = undefined, backUpToDynamic, walletMetadata }) {
|
|
1048
1165
|
this.ensureApiClientAuthenticated();
|
|
1049
|
-
|
|
1050
|
-
await this.ensureCeremonyCompletionBeforeBackup({
|
|
1051
|
-
accountAddress
|
|
1052
|
-
});
|
|
1166
|
+
this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
|
|
1053
1167
|
const dynamicRequestId = v4();
|
|
1054
1168
|
try {
|
|
1055
1169
|
const keySharesToBackup = externalServerKeyShares != null ? externalServerKeyShares : await this.getExternalServerKeyShares({
|
|
1056
1170
|
accountAddress,
|
|
1057
|
-
password
|
|
1171
|
+
password,
|
|
1172
|
+
walletMetadata
|
|
1058
1173
|
});
|
|
1059
1174
|
if (!keySharesToBackup || keySharesToBackup.length === 0) {
|
|
1060
|
-
throw new Error(
|
|
1175
|
+
throw new Error('Key shares not found for the requested wallet.');
|
|
1061
1176
|
}
|
|
1062
1177
|
const encryptedKeyShares = await Promise.all(keySharesToBackup.map((keyShare)=>this.encryptKeyShare({
|
|
1063
1178
|
keyShare,
|
|
1064
1179
|
password
|
|
1065
1180
|
})));
|
|
1066
|
-
|
|
1067
|
-
throw new Error(`WalletId not found for accountAddress: ${accountAddress}`);
|
|
1068
|
-
}
|
|
1181
|
+
const { walletId, chainName, addressType } = walletMetadata;
|
|
1069
1182
|
const passwordEncryptedFlag = Boolean(password) && password !== this.environmentId;
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
const bitcoinConfig = walletData.chainName === 'BTC' && walletData.addressType ? {
|
|
1073
|
-
addressType: walletData.addressType
|
|
1183
|
+
const bitcoinConfig = chainName === 'BTC' && addressType ? {
|
|
1184
|
+
addressType: addressType
|
|
1074
1185
|
} : undefined;
|
|
1075
1186
|
// Build locations + result for all shares
|
|
1076
1187
|
const locations = [];
|
|
1077
1188
|
const result = [];
|
|
1078
1189
|
// Upload first share to Dynamic if requested
|
|
1079
|
-
if (
|
|
1190
|
+
if (backUpToDynamic) {
|
|
1080
1191
|
const keyShareToBackupToDynamic = keySharesToBackup[0];
|
|
1081
1192
|
const data = await this.apiClient.storeEncryptedBackupByWallet({
|
|
1082
|
-
walletId
|
|
1193
|
+
walletId,
|
|
1083
1194
|
encryptedKeyShares: [
|
|
1084
1195
|
encryptedKeyShares[0]
|
|
1085
1196
|
],
|
|
@@ -1089,12 +1200,17 @@ class DynamicWalletClient {
|
|
|
1089
1200
|
dynamicRequestId
|
|
1090
1201
|
});
|
|
1091
1202
|
const keygenId = await this.getExportId({
|
|
1092
|
-
chainName
|
|
1203
|
+
chainName,
|
|
1093
1204
|
serverKeyShare: keyShareToBackupToDynamic,
|
|
1094
1205
|
bitcoinConfig
|
|
1095
1206
|
});
|
|
1096
1207
|
locations.push({
|
|
1097
1208
|
location: BackupLocation.DYNAMIC,
|
|
1209
|
+
// `id` and `externalKeyShareId` are the same server-assigned identifier
|
|
1210
|
+
// for dynamic backups; populating both keeps the synthesized backupInfo
|
|
1211
|
+
// shape identical to what `getExternalServerKeyShareBackupInfo` produces
|
|
1212
|
+
// from the API response (used by fetchWalletMetadata / getWalletByAddress).
|
|
1213
|
+
id: data.keyShareIds[0],
|
|
1098
1214
|
externalKeyShareId: data.keyShareIds[0],
|
|
1099
1215
|
passwordEncrypted: passwordEncryptedFlag,
|
|
1100
1216
|
keygenId
|
|
@@ -1104,10 +1220,10 @@ class DynamicWalletClient {
|
|
|
1104
1220
|
backedUpToClientKeyShareService: true
|
|
1105
1221
|
});
|
|
1106
1222
|
}
|
|
1107
|
-
const keySharesNotToBackupToDynamic =
|
|
1223
|
+
const keySharesNotToBackupToDynamic = backUpToDynamic ? keySharesToBackup.slice(1) : keySharesToBackup;
|
|
1108
1224
|
for (const keyShare of keySharesNotToBackupToDynamic){
|
|
1109
1225
|
const keygenId = await this.getExportId({
|
|
1110
|
-
chainName
|
|
1226
|
+
chainName,
|
|
1111
1227
|
serverKeyShare: keyShare,
|
|
1112
1228
|
bitcoinConfig
|
|
1113
1229
|
});
|
|
@@ -1121,28 +1237,53 @@ class DynamicWalletClient {
|
|
|
1121
1237
|
backedUpToClientKeyShareService: false
|
|
1122
1238
|
});
|
|
1123
1239
|
}
|
|
1124
|
-
const
|
|
1125
|
-
walletId
|
|
1240
|
+
const markResponse = await this.apiClient.markKeySharesAsBackedUp({
|
|
1241
|
+
walletId,
|
|
1126
1242
|
locations,
|
|
1127
1243
|
dynamicRequestId
|
|
1128
1244
|
});
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1245
|
+
// Build backupInfo from the server response (`locationsWithKeyShares`) so
|
|
1246
|
+
// the cached backupInfo always agrees with what the server actually
|
|
1247
|
+
// stored — even if the server normalizes ids/keygenIds. The server
|
|
1248
|
+
// response does not include `passwordEncrypted` per location, so merge
|
|
1249
|
+
// the local `passwordEncryptedFlag` (a single boolean for this batch).
|
|
1250
|
+
// The fallback to local `locations` is defensive; warn loudly when it
|
|
1251
|
+
// fires so a server contract regression doesn't silently degrade.
|
|
1252
|
+
if (!(markResponse == null ? void 0 : markResponse.locationsWithKeyShares)) {
|
|
1253
|
+
this.logger.warn('markKeySharesAsBackedUp response missing locationsWithKeyShares; falling back to local synthesis. ' + 'This may indicate a server contract regression.');
|
|
1254
|
+
}
|
|
1255
|
+
var _markResponse_locationsWithKeyShares;
|
|
1256
|
+
const serverLocations = (_markResponse_locationsWithKeyShares = markResponse == null ? void 0 : markResponse.locationsWithKeyShares) != null ? _markResponse_locationsWithKeyShares : locations;
|
|
1257
|
+
const backupInfo = {
|
|
1258
|
+
backups: serverLocations.reduce((acc, loc)=>{
|
|
1259
|
+
// Defensive: if the server returns an unrecognized location enum
|
|
1260
|
+
// (e.g. a future BackupLocation we don't know yet), initialize
|
|
1261
|
+
// the bucket rather than throwing on `acc[unknown].push(...)`.
|
|
1262
|
+
if (!acc[loc.location]) {
|
|
1263
|
+
acc[loc.location] = [];
|
|
1264
|
+
}
|
|
1265
|
+
acc[loc.location].push({
|
|
1266
|
+
location: loc.location,
|
|
1267
|
+
id: loc.id,
|
|
1268
|
+
keygenId: loc.keygenId,
|
|
1269
|
+
externalKeyShareId: loc.externalKeyShareId,
|
|
1270
|
+
passwordEncrypted: passwordEncryptedFlag
|
|
1271
|
+
});
|
|
1272
|
+
return acc;
|
|
1273
|
+
}, {
|
|
1274
|
+
[BackupLocation.DYNAMIC]: [],
|
|
1275
|
+
[BackupLocation.GOOGLE_DRIVE]: [],
|
|
1276
|
+
[BackupLocation.ICLOUD]: [],
|
|
1277
|
+
[BackupLocation.USER]: [],
|
|
1278
|
+
[BackupLocation.EXTERNAL]: [],
|
|
1279
|
+
[BackupLocation.DELEGATED]: []
|
|
1280
|
+
}),
|
|
1281
|
+
passwordEncrypted: passwordEncryptedFlag
|
|
1282
|
+
};
|
|
1283
|
+
return {
|
|
1284
|
+
keySharesWithBackupStatus: result,
|
|
1285
|
+
backupInfo
|
|
1286
|
+
};
|
|
1146
1287
|
} catch (error) {
|
|
1147
1288
|
this.logger.error('Error in storeEncryptedBackupByWallet:', {
|
|
1148
1289
|
accountAddress,
|
|
@@ -1152,12 +1293,13 @@ class DynamicWalletClient {
|
|
|
1152
1293
|
throw error;
|
|
1153
1294
|
}
|
|
1154
1295
|
}
|
|
1155
|
-
async storeEncryptedBackupByWalletWithRetry({ accountAddress, externalServerKeyShares, password,
|
|
1296
|
+
async storeEncryptedBackupByWalletWithRetry({ accountAddress, externalServerKeyShares, password, backUpToDynamic, walletMetadata }) {
|
|
1156
1297
|
return retryPromise(()=>this.storeEncryptedBackupByWallet({
|
|
1157
1298
|
accountAddress,
|
|
1158
1299
|
externalServerKeyShares,
|
|
1159
1300
|
password,
|
|
1160
|
-
|
|
1301
|
+
backUpToDynamic,
|
|
1302
|
+
walletMetadata
|
|
1161
1303
|
}), {
|
|
1162
1304
|
operationName: 'store encrypted backup',
|
|
1163
1305
|
logContext: {
|
|
@@ -1165,15 +1307,12 @@ class DynamicWalletClient {
|
|
|
1165
1307
|
}
|
|
1166
1308
|
});
|
|
1167
1309
|
}
|
|
1168
|
-
async getExternalServerKeyShares({ accountAddress, password }) {
|
|
1169
|
-
var
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
password,
|
|
1173
|
-
walletOperation: WalletOperation.REACH_THRESHOLD
|
|
1174
|
-
});
|
|
1310
|
+
async getExternalServerKeyShares({ accountAddress, password, walletMetadata, backupInfo }) {
|
|
1311
|
+
var _resolvedBackupInfo_backups;
|
|
1312
|
+
this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
|
|
1313
|
+
const resolvedBackupInfo = backupInfo != null ? backupInfo : await this.resolveBackupInfo(walletMetadata);
|
|
1175
1314
|
// Check if the wallet has a dynamic backup and derive password encryption status from the first dynamic share
|
|
1176
|
-
const dynamicBackups = (
|
|
1315
|
+
const dynamicBackups = (resolvedBackupInfo == null ? void 0 : (_resolvedBackupInfo_backups = resolvedBackupInfo.backups) == null ? void 0 : _resolvedBackupInfo_backups.dynamic) || [];
|
|
1177
1316
|
const passwordEncrypted = dynamicBackups.length > 0 ? Boolean(dynamicBackups[0].passwordEncrypted) : false;
|
|
1178
1317
|
if (passwordEncrypted && !password) {
|
|
1179
1318
|
throw new Error('Password is required for decryption but not provided. This backup was encrypted with a password.');
|
|
@@ -1182,21 +1321,36 @@ class DynamicWalletClient {
|
|
|
1182
1321
|
const recoveredShares = await this.recoverEncryptedBackupByWallet({
|
|
1183
1322
|
accountAddress,
|
|
1184
1323
|
password,
|
|
1185
|
-
walletOperation: WalletOperation.REACH_THRESHOLD
|
|
1324
|
+
walletOperation: WalletOperation.REACH_THRESHOLD,
|
|
1325
|
+
walletMetadata,
|
|
1326
|
+
backupInfo: resolvedBackupInfo
|
|
1186
1327
|
});
|
|
1187
1328
|
return recoveredShares;
|
|
1188
1329
|
}
|
|
1189
|
-
async updatePassword({ accountAddress, existingPassword, newPassword,
|
|
1190
|
-
|
|
1330
|
+
async updatePassword({ accountAddress, existingPassword, newPassword, backUpToDynamic, externalServerKeyShares, walletMetadata }) {
|
|
1331
|
+
this.validatePasswordForBackup({
|
|
1332
|
+
password: newPassword,
|
|
1333
|
+
backUpToDynamic
|
|
1334
|
+
});
|
|
1335
|
+
this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
|
|
1336
|
+
const sharesToBackup = await this.resolveKeyShares({
|
|
1191
1337
|
accountAddress,
|
|
1192
1338
|
password: existingPassword,
|
|
1193
|
-
walletOperation: WalletOperation.REACH_ALL_PARTIES
|
|
1339
|
+
walletOperation: WalletOperation.REACH_ALL_PARTIES,
|
|
1340
|
+
externalServerKeyShares,
|
|
1341
|
+
errorMessage: 'External server key shares are required to update password',
|
|
1342
|
+
walletMetadata
|
|
1194
1343
|
});
|
|
1195
|
-
await this.storeEncryptedBackupByWallet({
|
|
1344
|
+
const { backupInfo } = await this.storeEncryptedBackupByWallet({
|
|
1196
1345
|
accountAddress,
|
|
1197
1346
|
password: newPassword,
|
|
1198
|
-
|
|
1347
|
+
backUpToDynamic,
|
|
1348
|
+
externalServerKeyShares: sharesToBackup,
|
|
1349
|
+
walletMetadata
|
|
1199
1350
|
});
|
|
1351
|
+
return {
|
|
1352
|
+
backupInfo
|
|
1353
|
+
};
|
|
1200
1354
|
}
|
|
1201
1355
|
async decryptKeyShare({ keyShare, password }) {
|
|
1202
1356
|
const decodedKeyShare = JSON.parse(Buffer.from(keyShare, 'base64').toString());
|
|
@@ -1241,49 +1395,45 @@ class DynamicWalletClient {
|
|
|
1241
1395
|
};
|
|
1242
1396
|
}
|
|
1243
1397
|
/**
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
* @param accountAddress - The account address to
|
|
1398
|
+
* Resolves and returns key shares for wallet operations.
|
|
1399
|
+
* Checks in order: provided shares, then attempts recovery from backup.
|
|
1400
|
+
* @param accountAddress - The account address to get shares for
|
|
1247
1401
|
* @param password - The password to decrypt the shares
|
|
1248
1402
|
* @param walletOperation - The wallet operation being performed
|
|
1249
1403
|
* @param externalServerKeyShares - The provided key shares (if any)
|
|
1250
|
-
* @param errorMessage - The error message to throw if
|
|
1251
|
-
* @
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1404
|
+
* @param errorMessage - The error message to throw if no shares are found
|
|
1405
|
+
* @param walletMetadata - Required wallet metadata identifying the target wallet
|
|
1406
|
+
* @returns The resolved key shares
|
|
1407
|
+
* @throws Error if no shares are found and recovery fails
|
|
1408
|
+
*/ async resolveKeyShares({ accountAddress, password, walletOperation, externalServerKeyShares, errorMessage, walletMetadata, backupInfo }) {
|
|
1409
|
+
if (externalServerKeyShares == null ? void 0 : externalServerKeyShares.length) {
|
|
1410
|
+
return externalServerKeyShares;
|
|
1411
|
+
}
|
|
1412
|
+
const recoveredShares = await this.recoverEncryptedBackupByWallet({
|
|
1413
|
+
accountAddress,
|
|
1414
|
+
password,
|
|
1415
|
+
walletOperation,
|
|
1416
|
+
walletMetadata,
|
|
1417
|
+
backupInfo
|
|
1418
|
+
});
|
|
1419
|
+
if (!recoveredShares || recoveredShares.length === 0) {
|
|
1420
|
+
throw new Error(errorMessage);
|
|
1266
1421
|
}
|
|
1422
|
+
return recoveredShares;
|
|
1267
1423
|
}
|
|
1268
|
-
async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, shareCount = undefined,
|
|
1269
|
-
var _wallet_externalServerKeySharesBackupInfo;
|
|
1424
|
+
async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, shareCount = undefined, walletMetadata, backupInfo }) {
|
|
1270
1425
|
this.ensureApiClientAuthenticated();
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
throw new Error(`Wallet not found for address 1: ${accountAddress}`);
|
|
1276
|
-
}
|
|
1277
|
-
wallet = fetchedWallet;
|
|
1278
|
-
}
|
|
1426
|
+
this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
|
|
1427
|
+
const walletId = walletMetadata.walletId;
|
|
1428
|
+
const thresholdSignatureScheme = walletMetadata.thresholdSignatureScheme;
|
|
1429
|
+
const externalServerKeySharesBackupInfo = backupInfo != null ? backupInfo : await this.resolveBackupInfo(walletMetadata);
|
|
1279
1430
|
// PII: intentionally omitting full wallet object (contains key share info)
|
|
1280
1431
|
this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, {
|
|
1281
|
-
walletId
|
|
1282
|
-
chainName: wallet.chainName
|
|
1432
|
+
walletId
|
|
1283
1433
|
});
|
|
1284
1434
|
const { shares } = this.recoverStrategy({
|
|
1285
|
-
externalServerKeySharesBackupInfo
|
|
1286
|
-
thresholdSignatureScheme
|
|
1435
|
+
externalServerKeySharesBackupInfo,
|
|
1436
|
+
thresholdSignatureScheme,
|
|
1287
1437
|
walletOperation,
|
|
1288
1438
|
shareCount
|
|
1289
1439
|
});
|
|
@@ -1293,13 +1443,13 @@ class DynamicWalletClient {
|
|
|
1293
1443
|
return [];
|
|
1294
1444
|
}
|
|
1295
1445
|
const data = await this.apiClient.recoverEncryptedBackupByWallet({
|
|
1296
|
-
walletId
|
|
1446
|
+
walletId,
|
|
1297
1447
|
externalKeyShareIds: dynamicKeyShareIds,
|
|
1298
1448
|
requiresSignedSessionId: false
|
|
1299
1449
|
});
|
|
1300
1450
|
const dynamicKeyShares = data.keyShares.filter((keyShare)=>keyShare.encryptedAccountCredential !== null && keyShare.backupLocation === BackupLocation.DYNAMIC);
|
|
1301
|
-
var
|
|
1302
|
-
const isPasswordEncrypted = (
|
|
1451
|
+
var _externalServerKeySharesBackupInfo_passwordEncrypted;
|
|
1452
|
+
const isPasswordEncrypted = (_externalServerKeySharesBackupInfo_passwordEncrypted = externalServerKeySharesBackupInfo == null ? void 0 : externalServerKeySharesBackupInfo.passwordEncrypted) != null ? _externalServerKeySharesBackupInfo_passwordEncrypted : false;
|
|
1303
1453
|
if (isPasswordEncrypted && !password) {
|
|
1304
1454
|
throw new Error('Password is required for decryption but not provided. This backup was encrypted with a password.');
|
|
1305
1455
|
}
|
|
@@ -1307,103 +1457,37 @@ class DynamicWalletClient {
|
|
|
1307
1457
|
keyShare: keyShare.encryptedAccountCredential,
|
|
1308
1458
|
password: password != null ? password : this.environmentId
|
|
1309
1459
|
})));
|
|
1310
|
-
if (storeRecoveredShares) {
|
|
1311
|
-
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1312
|
-
externalServerKeyShares: mergeUniqueKeyShares(this.walletMap[accountAddress].externalServerKeyShares || [], decryptedKeyShares)
|
|
1313
|
-
});
|
|
1314
|
-
}
|
|
1315
1460
|
return decryptedKeyShares;
|
|
1316
1461
|
}
|
|
1317
|
-
async exportExternalServerKeyShares({ accountAddress, password }) {
|
|
1462
|
+
async exportExternalServerKeyShares({ accountAddress, password, walletMetadata }) {
|
|
1463
|
+
this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
|
|
1464
|
+
const backupInfo = await this.resolveBackupInfo(walletMetadata);
|
|
1318
1465
|
await this.verifyPassword({
|
|
1319
1466
|
accountAddress,
|
|
1320
|
-
password
|
|
1467
|
+
password,
|
|
1468
|
+
walletMetadata,
|
|
1469
|
+
backupInfo
|
|
1321
1470
|
});
|
|
1322
1471
|
const externalServerKeyShares = await this.getExternalServerKeyShares({
|
|
1323
1472
|
accountAddress,
|
|
1324
|
-
password
|
|
1473
|
+
password,
|
|
1474
|
+
walletMetadata,
|
|
1475
|
+
backupInfo
|
|
1325
1476
|
});
|
|
1326
1477
|
return externalServerKeyShares;
|
|
1327
1478
|
}
|
|
1328
1479
|
/**
|
|
1329
|
-
* Helper function to check if the required wallet fields are present and valid
|
|
1330
|
-
* @param accountAddress - The account address of the wallet to check
|
|
1331
|
-
* @param walletOperation - The wallet operation that determines required fields
|
|
1332
|
-
* @returns boolean indicating if wallet needs to be re-fetched and restored from server
|
|
1333
|
-
*/ async checkWalletFields({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD, shareCount }) {
|
|
1334
|
-
var _existingWallet_externalServerKeyShares;
|
|
1335
|
-
let keyshareCheck = false;
|
|
1336
|
-
let walletCheck = false;
|
|
1337
|
-
let thresholdSignatureSchemeCheck = false;
|
|
1338
|
-
let derivationPathCheck = false;
|
|
1339
|
-
// check if wallet exists
|
|
1340
|
-
const existingWallet = this.walletMap[accountAddress];
|
|
1341
|
-
if (existingWallet) {
|
|
1342
|
-
walletCheck = true;
|
|
1343
|
-
}
|
|
1344
|
-
// check if threshold signature scheme exists
|
|
1345
|
-
if (existingWallet == null ? void 0 : existingWallet.thresholdSignatureScheme) {
|
|
1346
|
-
thresholdSignatureSchemeCheck = true;
|
|
1347
|
-
}
|
|
1348
|
-
// check if derivation path exists
|
|
1349
|
-
if ((existingWallet == null ? void 0 : existingWallet.derivationPath) || (existingWallet == null ? void 0 : existingWallet.derivationPath) === '') {
|
|
1350
|
-
derivationPathCheck = true;
|
|
1351
|
-
}
|
|
1352
|
-
// check if wallet already exists with sufficient keyshares
|
|
1353
|
-
if (existingWallet) {
|
|
1354
|
-
var _existingWallet_externalServerKeyShares1;
|
|
1355
|
-
const { shares } = this.recoverStrategy({
|
|
1356
|
-
externalServerKeySharesBackupInfo: existingWallet.externalServerKeySharesBackupInfo || {
|
|
1357
|
-
backups: getExternalServerKeyShareBackupInfo()
|
|
1358
|
-
},
|
|
1359
|
-
thresholdSignatureScheme: existingWallet.thresholdSignatureScheme,
|
|
1360
|
-
walletOperation,
|
|
1361
|
-
shareCount
|
|
1362
|
-
});
|
|
1363
|
-
const { dynamic: requiredDynamicKeyShareIds = [] } = shares;
|
|
1364
|
-
const { external: requiredExternalKeyShareIds = [] } = shares;
|
|
1365
|
-
// Check if we have enough shares from any backup location
|
|
1366
|
-
const totalRequiredShares = requiredDynamicKeyShareIds.length + requiredExternalKeyShareIds.length;
|
|
1367
|
-
// Check if we have the required shares either loaded OR available in backup
|
|
1368
|
-
const hasLoadedShares = totalRequiredShares <= (((_existingWallet_externalServerKeyShares1 = existingWallet.externalServerKeyShares) == null ? void 0 : _existingWallet_externalServerKeyShares1.length) || 0);
|
|
1369
|
-
// Check if backup contains the specific required share IDs
|
|
1370
|
-
const hasBackupShares = existingWallet.externalServerKeySharesBackupInfo && (()=>{
|
|
1371
|
-
const backupShares = existingWallet.externalServerKeySharesBackupInfo.backups.dynamic;
|
|
1372
|
-
const allRequiredIds = [
|
|
1373
|
-
...requiredDynamicKeyShareIds,
|
|
1374
|
-
...requiredExternalKeyShareIds
|
|
1375
|
-
];
|
|
1376
|
-
return allRequiredIds.every((requiredId)=>backupShares.some((backupShare)=>backupShare.externalKeyShareId === requiredId));
|
|
1377
|
-
})();
|
|
1378
|
-
if (hasLoadedShares || hasBackupShares) {
|
|
1379
|
-
keyshareCheck = true;
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
const result = walletCheck && thresholdSignatureSchemeCheck && keyshareCheck && derivationPathCheck;
|
|
1383
|
-
this.logger.debug('Wallet checks:', {
|
|
1384
|
-
walletCheck,
|
|
1385
|
-
thresholdSignatureSchemeCheck,
|
|
1386
|
-
keyshareCheck,
|
|
1387
|
-
derivationPathCheck,
|
|
1388
|
-
existingWallet: !!existingWallet,
|
|
1389
|
-
keySharesLength: existingWallet == null ? void 0 : (_existingWallet_externalServerKeyShares = existingWallet.externalServerKeyShares) == null ? void 0 : _existingWallet_externalServerKeyShares.length,
|
|
1390
|
-
result
|
|
1391
|
-
});
|
|
1392
|
-
return result;
|
|
1393
|
-
}
|
|
1394
|
-
/**
|
|
1395
1480
|
* verifyPassword attempts to recover and decrypt a single client key share using the provided password.
|
|
1396
1481
|
* If successful, the key share is encrypted with the new password. This method solely performs the recovery
|
|
1397
1482
|
* and decryption without storing the restored key shares. If unsuccessful, it throws an error.
|
|
1398
|
-
*/ async verifyPassword({ accountAddress, password = undefined }) {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
password,
|
|
1402
|
-
walletOperation: WalletOperation.NO_OPERATION
|
|
1403
|
-
});
|
|
1483
|
+
*/ async verifyPassword({ accountAddress, password = undefined, walletMetadata, backupInfo }) {
|
|
1484
|
+
this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
|
|
1485
|
+
const resolvedBackupInfo = backupInfo != null ? backupInfo : await this.resolveBackupInfo(walletMetadata);
|
|
1404
1486
|
if (await this.requiresPasswordForOperation({
|
|
1405
1487
|
accountAddress,
|
|
1406
|
-
walletOperation: WalletOperation.NO_OPERATION
|
|
1488
|
+
walletOperation: WalletOperation.NO_OPERATION,
|
|
1489
|
+
walletMetadata,
|
|
1490
|
+
backupInfo: resolvedBackupInfo
|
|
1407
1491
|
}) && !password) {
|
|
1408
1492
|
throw new Error('Password is required for operation but not provided');
|
|
1409
1493
|
}
|
|
@@ -1411,9 +1495,7 @@ class DynamicWalletClient {
|
|
|
1411
1495
|
if (!password) {
|
|
1412
1496
|
return;
|
|
1413
1497
|
}
|
|
1414
|
-
const { backups } =
|
|
1415
|
-
accountAddress
|
|
1416
|
-
});
|
|
1498
|
+
const { backups } = resolvedBackupInfo;
|
|
1417
1499
|
const { dynamic: dynamicKeyShareIds = [] } = backups;
|
|
1418
1500
|
const { external: externalKeyShareIds = [] } = backups;
|
|
1419
1501
|
// Check if we have any shares available (DYNAMIC or EXTERNAL)
|
|
@@ -1421,180 +1503,105 @@ class DynamicWalletClient {
|
|
|
1421
1503
|
throw new Error('No key shares found');
|
|
1422
1504
|
}
|
|
1423
1505
|
try {
|
|
1424
|
-
// Recover and store shares to unlock the wallet (storeRecoveredShares defaults to true)
|
|
1425
1506
|
await this.recoverEncryptedBackupByWallet({
|
|
1426
1507
|
accountAddress,
|
|
1427
1508
|
password,
|
|
1428
|
-
walletOperation: WalletOperation.NO_OPERATION
|
|
1509
|
+
walletOperation: WalletOperation.NO_OPERATION,
|
|
1510
|
+
walletMetadata,
|
|
1511
|
+
backupInfo: resolvedBackupInfo
|
|
1429
1512
|
});
|
|
1430
1513
|
} catch (error) {
|
|
1431
1514
|
this.logger.error('Error in verifying password', error);
|
|
1432
|
-
|
|
1515
|
+
// We can't distinguish wrong-password from stale-metadata without a
|
|
1516
|
+
// server-side backupInfo lookup (fetchWalletMetadata returns identity
|
|
1517
|
+
// only). Surface both possibilities so customers know where to look.
|
|
1518
|
+
throw new Error('Failed to verify password. Either the password is incorrect, or the cached `walletMetadata.externalServerKeySharesBackupInfo` is stale ' + '(merge the `backupInfo` returned from updatePassword/refreshWalletAccountShares/reshare into your cached walletMetadata).');
|
|
1433
1519
|
}
|
|
1434
1520
|
}
|
|
1435
|
-
async isPasswordEncrypted({ accountAddress }) {
|
|
1436
|
-
const externalServerKeySharesBackupInfo = await this.getWalletExternalServerKeyShareBackupInfo({
|
|
1437
|
-
|
|
1521
|
+
async isPasswordEncrypted({ accountAddress, walletMetadata, backupInfo }) {
|
|
1522
|
+
const externalServerKeySharesBackupInfo = backupInfo != null ? backupInfo : await this.getWalletExternalServerKeyShareBackupInfo({
|
|
1523
|
+
walletMetadata
|
|
1438
1524
|
});
|
|
1439
1525
|
return externalServerKeySharesBackupInfo == null ? void 0 : externalServerKeySharesBackupInfo.passwordEncrypted;
|
|
1440
1526
|
}
|
|
1441
1527
|
/**
|
|
1442
1528
|
* check if the operation requires a password
|
|
1443
|
-
*/ async requiresPasswordForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD }) {
|
|
1529
|
+
*/ async requiresPasswordForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD, walletMetadata, backupInfo }) {
|
|
1444
1530
|
const isEncrypted = await this.isPasswordEncrypted({
|
|
1445
|
-
accountAddress
|
|
1531
|
+
accountAddress,
|
|
1532
|
+
walletMetadata,
|
|
1533
|
+
backupInfo
|
|
1446
1534
|
});
|
|
1447
1535
|
if (!isEncrypted) {
|
|
1448
1536
|
return false;
|
|
1449
1537
|
}
|
|
1450
1538
|
return this.requiresRestoreBackupSharesForOperation({
|
|
1451
1539
|
accountAddress,
|
|
1452
|
-
walletOperation
|
|
1540
|
+
walletOperation,
|
|
1541
|
+
walletMetadata,
|
|
1542
|
+
backupInfo
|
|
1453
1543
|
});
|
|
1454
1544
|
}
|
|
1455
1545
|
/**
|
|
1456
1546
|
* check if the operation requires restoring backup shares
|
|
1457
|
-
*/ async requiresRestoreBackupSharesForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD }) {
|
|
1458
|
-
const externalServerKeySharesBackupInfo = await this.getWalletExternalServerKeyShareBackupInfo({
|
|
1459
|
-
|
|
1547
|
+
*/ async requiresRestoreBackupSharesForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD, walletMetadata, backupInfo }) {
|
|
1548
|
+
const externalServerKeySharesBackupInfo = backupInfo != null ? backupInfo : await this.getWalletExternalServerKeyShareBackupInfo({
|
|
1549
|
+
walletMetadata
|
|
1460
1550
|
});
|
|
1461
|
-
const
|
|
1551
|
+
const thresholdSignatureScheme = walletMetadata.thresholdSignatureScheme;
|
|
1462
1552
|
if (walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.RESHARE) {
|
|
1463
1553
|
return true;
|
|
1464
1554
|
}
|
|
1465
1555
|
const { shares, requiredShareCount } = this.recoverStrategy({
|
|
1466
1556
|
externalServerKeySharesBackupInfo,
|
|
1467
|
-
thresholdSignatureScheme
|
|
1557
|
+
thresholdSignatureScheme,
|
|
1468
1558
|
walletOperation
|
|
1469
1559
|
});
|
|
1470
1560
|
const dynamicKeyShareIds = shares[BackupLocation.DYNAMIC] || [];
|
|
1471
1561
|
const externalKeyShareIds = shares[BackupLocation.EXTERNAL] || [];
|
|
1472
|
-
|
|
1473
|
-
const totalAvailableShares = externalServerKeyShares.length + dynamicKeyShareIds.length + externalKeyShareIds.length;
|
|
1562
|
+
const totalAvailableShares = dynamicKeyShareIds.length + externalKeyShareIds.length;
|
|
1474
1563
|
if (totalAvailableShares >= requiredShareCount) {
|
|
1475
1564
|
return false;
|
|
1476
1565
|
}
|
|
1477
1566
|
return true;
|
|
1478
1567
|
}
|
|
1479
|
-
async getWalletExternalServerKeyShareBackupInfo({
|
|
1480
|
-
var _wallet_externalServerKeySharesBackupInfo_backups_BackupLocation_DYNAMIC, _wallet_externalServerKeySharesBackupInfo_backups, _wallet_externalServerKeySharesBackupInfo, _user_verifiedCredentials;
|
|
1568
|
+
async getWalletExternalServerKeyShareBackupInfo({ walletMetadata }) {
|
|
1481
1569
|
this.ensureApiClientAuthenticated();
|
|
1482
|
-
|
|
1483
|
-
if (!wallet) {
|
|
1484
|
-
const fetchedWallet = await this.getWalletByAddress(accountAddress);
|
|
1485
|
-
if (!fetchedWallet) {
|
|
1486
|
-
throw new Error(`Wallet not found for address 2: ${accountAddress}`);
|
|
1487
|
-
}
|
|
1488
|
-
wallet = fetchedWallet;
|
|
1489
|
-
}
|
|
1490
|
-
// Return existing backup info if it exists
|
|
1491
|
-
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) {
|
|
1492
|
-
return wallet.externalServerKeySharesBackupInfo;
|
|
1493
|
-
}
|
|
1494
|
-
// Get backup info from server
|
|
1495
|
-
const user = await this.apiClient.getUser(v4());
|
|
1496
|
-
const walletData = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>{
|
|
1497
|
-
var _vc_address;
|
|
1498
|
-
return ((_vc_address = vc.address) == null ? void 0 : _vc_address.toLowerCase()) === accountAddress.toLowerCase();
|
|
1499
|
-
});
|
|
1500
|
-
return getExternalServerKeyShareBackupInfo({
|
|
1501
|
-
walletProperties: walletData == null ? void 0 : walletData.walletProperties
|
|
1502
|
-
});
|
|
1503
|
-
}
|
|
1504
|
-
async getWallet({ accountAddress, walletOperation = WalletOperation.NO_OPERATION, shareCount = undefined, password = undefined }) {
|
|
1505
|
-
try {
|
|
1506
|
-
this.ensureApiClientAuthenticated();
|
|
1507
|
-
const existingWalletCheck = await this.checkWalletFields({
|
|
1508
|
-
accountAddress,
|
|
1509
|
-
walletOperation,
|
|
1510
|
-
shareCount
|
|
1511
|
-
});
|
|
1512
|
-
if (existingWalletCheck) {
|
|
1513
|
-
this.logger.debug(`Wallet ${accountAddress} already exists`);
|
|
1514
|
-
return this.walletMap[accountAddress];
|
|
1515
|
-
}
|
|
1516
|
-
const wallet = await this.getWalletByAddress(accountAddress);
|
|
1517
|
-
if (!wallet) {
|
|
1518
|
-
throw new Error(`Wallet not found for address 3: ${accountAddress}`);
|
|
1519
|
-
}
|
|
1520
|
-
// PII: intentionally omitting full wallet object (contains key share info)
|
|
1521
|
-
this.logger.debug('Restoring wallet', {
|
|
1522
|
-
walletId: wallet.walletId,
|
|
1523
|
-
chainName: wallet.chainName
|
|
1524
|
-
});
|
|
1525
|
-
// The wallet is already processed, so we can use it directly
|
|
1526
|
-
this.walletMap[accountAddress] = wallet;
|
|
1527
|
-
if (walletOperation !== WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
|
|
1528
|
-
accountAddress,
|
|
1529
|
-
walletOperation
|
|
1530
|
-
})) {
|
|
1531
|
-
const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
|
|
1532
|
-
accountAddress,
|
|
1533
|
-
password: password != null ? password : this.environmentId,
|
|
1534
|
-
walletOperation: walletOperation,
|
|
1535
|
-
shareCount
|
|
1536
|
-
});
|
|
1537
|
-
// PII: intentionally omitting decryptedKeyShares (contains key material)
|
|
1538
|
-
this.logger.debug('[DynamicWaasWalletClient] Recovered backup', {
|
|
1539
|
-
shareCount: decryptedKeyShares == null ? void 0 : decryptedKeyShares.length
|
|
1540
|
-
});
|
|
1541
|
-
}
|
|
1542
|
-
// externalServerKeyShares
|
|
1543
|
-
const walletCount = Object.keys(this.walletMap).length;
|
|
1544
|
-
if (walletCount === 0) {
|
|
1545
|
-
throw new Error('No wallets found');
|
|
1546
|
-
}
|
|
1547
|
-
// Return the only wallet if there's just one
|
|
1548
|
-
if (walletCount === 1) {
|
|
1549
|
-
return Object.values(this.walletMap)[0];
|
|
1550
|
-
}
|
|
1551
|
-
return this.walletMap[accountAddress];
|
|
1552
|
-
} catch (error) {
|
|
1553
|
-
this.logger.error('Error in getWallet', error);
|
|
1554
|
-
throw error;
|
|
1555
|
-
}
|
|
1570
|
+
return this.resolveBackupInfo(walletMetadata);
|
|
1556
1571
|
}
|
|
1557
1572
|
/**
|
|
1558
|
-
* Get a single wallet by address
|
|
1559
|
-
* First tries the efficient getWaasWalletByAddress endpoint, falls back to
|
|
1573
|
+
* Get a single wallet by address.
|
|
1574
|
+
* First tries the efficient `getWaasWalletByAddress` endpoint, falls back to
|
|
1575
|
+
* `getUser()` if not available.
|
|
1576
|
+
*
|
|
1577
|
+
* Note: the `getWaasWalletByAddress` endpoint returns wallet identity only
|
|
1578
|
+
* — it does not include keyShare pointer metadata, so the byAddress path
|
|
1579
|
+
* leaves `externalServerKeySharesBackupInfo` `undefined`. Operations that
|
|
1580
|
+
* need it (sign with auto-recovery, password verification, refresh,
|
|
1581
|
+
* reshare, updatePassword) will throw on a result obtained via this path.
|
|
1582
|
+
* The `getUser()` fallback populates `externalServerKeySharesBackupInfo`
|
|
1583
|
+
* from `walletProperties.keyShares`.
|
|
1584
|
+
*
|
|
1585
|
+
* Prefer `fetchWalletMetadata(accountAddress)` for identity-only lookups.
|
|
1560
1586
|
*/ async getWalletByAddress(accountAddress) {
|
|
1561
|
-
// Return cached wallet if available
|
|
1562
|
-
if (this.walletMap[accountAddress]) {
|
|
1563
|
-
return this.walletMap[accountAddress];
|
|
1564
|
-
}
|
|
1565
1587
|
this.ensureApiClientAuthenticated();
|
|
1566
|
-
// Try getting single wallet by address first
|
|
1567
1588
|
try {
|
|
1568
|
-
var _walletResponse_wallet;
|
|
1569
1589
|
const walletResponse = await this.apiClient.getWaasWalletByAddress({
|
|
1570
1590
|
walletAddress: accountAddress
|
|
1571
1591
|
});
|
|
1572
|
-
|
|
1592
|
+
return {
|
|
1573
1593
|
walletId: walletResponse.wallet.walletId,
|
|
1574
1594
|
chainName: walletResponse.wallet.chainName,
|
|
1575
1595
|
accountAddress: walletResponse.wallet.accountAddress,
|
|
1576
1596
|
externalServerKeyShares: [],
|
|
1577
1597
|
derivationPath: walletResponse.wallet.derivationPath,
|
|
1578
|
-
thresholdSignatureScheme: walletResponse.wallet.thresholdSignatureScheme
|
|
1579
|
-
externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo({
|
|
1580
|
-
walletProperties: {
|
|
1581
|
-
// @ts-expect-error TODO: update response to get key shares
|
|
1582
|
-
keyShares: ((_walletResponse_wallet = walletResponse.wallet) == null ? void 0 : _walletResponse_wallet.keyShares) || [],
|
|
1583
|
-
thresholdSignatureScheme: walletResponse.wallet.thresholdSignatureScheme,
|
|
1584
|
-
derivationPath: walletResponse.wallet.derivationPath
|
|
1585
|
-
}
|
|
1586
|
-
})
|
|
1598
|
+
thresholdSignatureScheme: walletResponse.wallet.thresholdSignatureScheme
|
|
1587
1599
|
};
|
|
1588
|
-
// Cache the wallet
|
|
1589
|
-
this.walletMap[accountAddress] = walletProperties;
|
|
1590
|
-
return walletProperties;
|
|
1591
1600
|
} catch (error) {
|
|
1592
1601
|
var _error_response;
|
|
1593
|
-
// If the new endpoint doesn't exist (404 or not implemented), fall back to getUser()
|
|
1594
1602
|
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') {
|
|
1595
1603
|
var _user_verifiedCredentials, _wallet_walletProperties, _wallet_walletProperties1;
|
|
1596
1604
|
this.logger.debug('getWaasWalletByAddress endpoint not available, falling back to getUser()');
|
|
1597
|
-
// Fallback to getUser() approach
|
|
1598
1605
|
const user = await this.apiClient.getUser(v4());
|
|
1599
1606
|
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>{
|
|
1600
1607
|
var _vc_address;
|
|
@@ -1604,9 +1611,9 @@ class DynamicWalletClient {
|
|
|
1604
1611
|
return null;
|
|
1605
1612
|
}
|
|
1606
1613
|
var _wallet_walletProperties_derivationPath;
|
|
1607
|
-
|
|
1614
|
+
return {
|
|
1608
1615
|
walletId: wallet.id,
|
|
1609
|
-
chainName: wallet.chain,
|
|
1616
|
+
chainName: verifiedCredentialNameToChainEnum[wallet.chain],
|
|
1610
1617
|
accountAddress: wallet.address,
|
|
1611
1618
|
externalServerKeyShares: [],
|
|
1612
1619
|
derivationPath: (_wallet_walletProperties_derivationPath = (_wallet_walletProperties = wallet.walletProperties) == null ? void 0 : _wallet_walletProperties.derivationPath) != null ? _wallet_walletProperties_derivationPath : undefined,
|
|
@@ -1615,9 +1622,6 @@ class DynamicWalletClient {
|
|
|
1615
1622
|
walletProperties: wallet.walletProperties || {}
|
|
1616
1623
|
})
|
|
1617
1624
|
};
|
|
1618
|
-
// Cache the wallet
|
|
1619
|
-
this.walletMap[accountAddress] = walletProperties;
|
|
1620
|
-
return walletProperties;
|
|
1621
1625
|
}
|
|
1622
1626
|
throw error;
|
|
1623
1627
|
}
|
|
@@ -1632,8 +1636,8 @@ class DynamicWalletClient {
|
|
|
1632
1636
|
var _user_verifiedCredentials;
|
|
1633
1637
|
const verifiedCredentials = (_user_verifiedCredentials = user.verifiedCredentials) != null ? _user_verifiedCredentials : [];
|
|
1634
1638
|
const waasWallets = verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas' && !!vc.address && !!vc.chain);
|
|
1635
|
-
|
|
1636
|
-
var
|
|
1639
|
+
return waasWallets.map((vc)=>{
|
|
1640
|
+
var _vc_walletProperties, _vc_walletProperties1;
|
|
1637
1641
|
var _vc_walletProperties_thresholdSignatureScheme;
|
|
1638
1642
|
return {
|
|
1639
1643
|
walletId: vc.id,
|
|
@@ -1642,49 +1646,13 @@ class DynamicWalletClient {
|
|
|
1642
1646
|
externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo({
|
|
1643
1647
|
walletProperties: vc.walletProperties
|
|
1644
1648
|
}),
|
|
1645
|
-
externalServerKeyShares:
|
|
1649
|
+
externalServerKeyShares: [],
|
|
1646
1650
|
derivationPath: (_vc_walletProperties = vc.walletProperties) == null ? void 0 : _vc_walletProperties.derivationPath,
|
|
1647
1651
|
thresholdSignatureScheme: (_vc_walletProperties_thresholdSignatureScheme = (_vc_walletProperties1 = vc.walletProperties) == null ? void 0 : _vc_walletProperties1.thresholdSignatureScheme) != null ? _vc_walletProperties_thresholdSignatureScheme : ThresholdSignatureScheme.TWO_OF_TWO
|
|
1648
1652
|
};
|
|
1649
1653
|
});
|
|
1650
|
-
this.walletMap = wallets.reduce((acc, wallet)=>{
|
|
1651
|
-
acc[wallet.accountAddress] = wallet;
|
|
1652
|
-
return acc;
|
|
1653
|
-
}, {});
|
|
1654
|
-
return wallets;
|
|
1655
|
-
}
|
|
1656
|
-
/**
|
|
1657
|
-
* Helper method to initialize or update a wallet entry in the walletMap.
|
|
1658
|
-
* This provides a consistent way to set up wallet properties across different operations.
|
|
1659
|
-
* @param accountAddress - The account address for the wallet
|
|
1660
|
-
* @param walletId - The wallet ID
|
|
1661
|
-
* @param chainName - The chain name (e.g., 'BTC', 'EVM')
|
|
1662
|
-
* @param thresholdSignatureScheme - The threshold signature scheme
|
|
1663
|
-
* @param derivationPath - Optional derivation path for the wallet
|
|
1664
|
-
* @param additionalProps - Optional additional properties to merge into the wallet entry
|
|
1665
|
-
*/ initializeWalletMapEntry({ accountAddress, walletId, chainName, thresholdSignatureScheme, derivationPath, additionalProps = {} }) {
|
|
1666
|
-
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1667
|
-
accountAddress,
|
|
1668
|
-
walletId,
|
|
1669
|
-
chainName,
|
|
1670
|
-
thresholdSignatureScheme
|
|
1671
|
-
}, derivationPath ? {
|
|
1672
|
-
derivationPath
|
|
1673
|
-
} : {}, {
|
|
1674
|
-
externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo()
|
|
1675
|
-
}, additionalProps);
|
|
1676
|
-
// PII: intentionally omitting walletMap (contains key share data for all wallets)
|
|
1677
|
-
this.logger.debug('walletMap initialized for wallet', {
|
|
1678
|
-
context: {
|
|
1679
|
-
accountAddress,
|
|
1680
|
-
walletId,
|
|
1681
|
-
chainName
|
|
1682
|
-
}
|
|
1683
|
-
});
|
|
1684
1654
|
}
|
|
1685
1655
|
constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, debug, forwardMPCClient, enableMPCAccelerator = true, logger }){
|
|
1686
|
-
this.walletMap = {} // todo: store in session storage
|
|
1687
|
-
;
|
|
1688
1656
|
this.isApiClientAuthenticated = false;
|
|
1689
1657
|
this.forwardMPCEnabled = true;
|
|
1690
1658
|
if (logger) {
|
|
@@ -1909,4 +1877,49 @@ const revokeDelegation = async (client, walletId)=>{
|
|
|
1909
1877
|
}
|
|
1910
1878
|
};
|
|
1911
1879
|
|
|
1912
|
-
|
|
1880
|
+
function decryptAesGcm(key, ivB64, ctB64, tagB64) {
|
|
1881
|
+
const iv = Buffer.from(ivB64, 'base64url');
|
|
1882
|
+
const ciphertext = Buffer.from(ctB64, 'base64url');
|
|
1883
|
+
const tag = Buffer.from(tagB64, 'base64url');
|
|
1884
|
+
const decipher = crypto$1.createDecipheriv('aes-256-gcm', key, iv);
|
|
1885
|
+
decipher.setAuthTag(tag);
|
|
1886
|
+
const updateResult = decipher.update(ciphertext);
|
|
1887
|
+
const finalResult = decipher.final();
|
|
1888
|
+
const plaintext = Buffer.concat([
|
|
1889
|
+
updateResult,
|
|
1890
|
+
finalResult
|
|
1891
|
+
]);
|
|
1892
|
+
return plaintext;
|
|
1893
|
+
}
|
|
1894
|
+
function rsaOaepDecryptEk(privateKeyPem, ekB64) {
|
|
1895
|
+
return crypto$1.privateDecrypt({
|
|
1896
|
+
key: privateKeyPem,
|
|
1897
|
+
oaepHash: 'sha256',
|
|
1898
|
+
padding: crypto$1.constants.RSA_PKCS1_OAEP_PADDING
|
|
1899
|
+
}, Buffer.from(ekB64, 'base64url'));
|
|
1900
|
+
}
|
|
1901
|
+
function decryptDelegatedWebhookData({ privateKeyPem, encryptedDelegatedKeyShare, encryptedWalletApiKey }) {
|
|
1902
|
+
try {
|
|
1903
|
+
const shareKey = rsaOaepDecryptEk(privateKeyPem, encryptedDelegatedKeyShare.ek);
|
|
1904
|
+
const walletApiKeyKey = rsaOaepDecryptEk(privateKeyPem, encryptedWalletApiKey.ek);
|
|
1905
|
+
const decryptedDelegatedShare = decryptAesGcm(shareKey, encryptedDelegatedKeyShare.iv, encryptedDelegatedKeyShare.ct, encryptedDelegatedKeyShare.tag);
|
|
1906
|
+
const decryptedWalletApiKey = decryptAesGcm(walletApiKeyKey, encryptedWalletApiKey.iv, encryptedWalletApiKey.ct, encryptedWalletApiKey.tag);
|
|
1907
|
+
const decryptedWalletApiKeyString = decryptedWalletApiKey.toString('utf8');
|
|
1908
|
+
const decryptedDelegatedShareString = decryptedDelegatedShare.toString('utf8');
|
|
1909
|
+
const decryptedDelegatedShareObject = JSON.parse(decryptedDelegatedShareString);
|
|
1910
|
+
if (!decryptedDelegatedShareObject) {
|
|
1911
|
+
throw new Error('Failed to decrypt delegated share');
|
|
1912
|
+
}
|
|
1913
|
+
if (!decryptedWalletApiKeyString) {
|
|
1914
|
+
throw new Error('Failed to decrypt wallet API key');
|
|
1915
|
+
}
|
|
1916
|
+
return {
|
|
1917
|
+
decryptedDelegatedShare: decryptedDelegatedShareObject,
|
|
1918
|
+
decryptedWalletApiKey: decryptedWalletApiKeyString
|
|
1919
|
+
};
|
|
1920
|
+
} catch (error) {
|
|
1921
|
+
throw new Error('Failed to decrypt delegated webhook data: ' + error);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
export { DynamicWalletClient, base64ToBytes, bytesToBase64, createDelegatedWalletClient, createLogError, decryptDelegatedWebhookData, delegatedSignMessage, ensureBase64Padding, formatEvmMessage, formatMessage, getExternalServerKeyShareBackupInfo, getMPCSignatureScheme, getMPCSigner, isHexString, logError, mergeUniqueKeyShares, retryPromise, revokeDelegation, stringToBytes, stripHexPrefix };
|