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