@dynamic-labs-wallet/browser 1.0.92 → 1.0.94
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 +273 -60
- package/index.esm.js +274 -63
- package/package.json +4 -3
- package/src/client.d.ts +42 -8
- package/src/client.d.ts.map +1 -1
- package/src/ed25519.d.ts +17 -0
- package/src/ed25519.d.ts.map +1 -0
- package/src/index.d.ts +1 -0
- package/src/index.d.ts.map +1 -1
- package/src/mpc/mpc.d.ts +13 -6
- package/src/mpc/mpc.d.ts.map +1 -1
- package/src/mpc/types.d.ts +5 -5
- package/src/mpc/types.d.ts.map +1 -1
- package/src/services/logger.d.ts +11 -2
- package/src/services/logger.d.ts.map +1 -1
- package/src/types.d.ts +9 -1
- package/src/types.d.ts.map +1 -1
- package/src/utils.d.ts +19 -0
- package/src/utils.d.ts.map +1 -1
package/index.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BitcoinAddressType, MPC_RELAY_PROD_API_URL, getMPCChainConfig, parseNamespacedVersion, WalletApiError, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, NETWORK_ERROR_STATUS, handleAxiosError, WalletOperation, WalletReadyState, classifyForwardMpcError, isRecoveredForwardMpcConnectionChurn, FEATURE_FLAGS, ThresholdSignatureScheme, getClientThreshold, MPC_CONFIG, getTSSConfig, serializeMessageForForwardMPC, isEvmCompatibleChain, serializeError, getReshareConfig, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, AuthMode, NoopLogger, DynamicApiClient, BusinessAccountClient, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP } from '@dynamic-labs-wallet/core';
|
|
1
|
+
import { BitcoinAddressType, MPC_RELAY_PROD_API_URL, DEFAULT_ED25519_VARIANT, getMPCChainConfig, parseNamespacedVersion, WalletApiError, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, NETWORK_ERROR_STATUS, handleAxiosError, WalletOperation, WalletReadyState, classifyForwardMpcError, isRecoveredForwardMpcConnectionChurn, FEATURE_FLAGS, ThresholdSignatureScheme, getClientThreshold, MPC_CONFIG, getTSSConfig, serializeMessageForForwardMPC, isEvmCompatibleChain, serializeError, getReshareConfig, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, AuthMode, NoopLogger, DynamicApiClient, BusinessAccountClient, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP } from '@dynamic-labs-wallet/core';
|
|
2
2
|
export * from '@dynamic-labs-wallet/core';
|
|
3
3
|
export { Logger } from '@dynamic-labs-wallet/core';
|
|
4
|
-
import { EdBls12377, BIP340, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, BIP340KeygenResult } from '#internal/web';
|
|
4
|
+
import { EdBls12377, BIP340, Ed25519, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, Ed25519KeygenResult, BIP340KeygenResult } from '#internal/web';
|
|
5
5
|
export { BIP340, BIP340InitKeygenResult, BIP340KeygenResult, Ecdsa, EcdsaInitKeygenResult, EcdsaKeygenResult, EcdsaPublicKey, EcdsaSignature, Ed25519, Ed25519InitKeygenResult, Ed25519KeygenResult, EdBls12377, EdBls12377KeygenResult, MessageHash } from '#internal/web';
|
|
6
6
|
import { v4, validate } from 'uuid';
|
|
7
7
|
import { SigningAlgorithm } from '@dynamic-labs-wallet/primitives';
|
|
@@ -10,6 +10,8 @@ import loadArgon2idWasm from 'argon2id';
|
|
|
10
10
|
import { AxiosError } from 'axios';
|
|
11
11
|
import gte from 'semver/functions/gte';
|
|
12
12
|
import PQueue from 'p-queue';
|
|
13
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
14
|
+
import { hexToBytes, bytesToNumberLE } from '@noble/curves/abstract/utils';
|
|
13
15
|
|
|
14
16
|
function _extends() {
|
|
15
17
|
_extends = Object.assign || function assign(target) {
|
|
@@ -79,12 +81,17 @@ function _extends() {
|
|
|
79
81
|
return undefined;
|
|
80
82
|
}
|
|
81
83
|
};
|
|
82
|
-
const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD_API_URL })=>{
|
|
84
|
+
const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD_API_URL, variant = DEFAULT_ED25519_VARIANT })=>{
|
|
83
85
|
switch(signingAlgorithm){
|
|
84
86
|
case SigningAlgorithm.ECDSA:
|
|
85
87
|
return new Ecdsa(baseRelayUrl);
|
|
86
88
|
case SigningAlgorithm.ED25519:
|
|
87
|
-
|
|
89
|
+
// `Ed25519` (non-exportable) and `ExportableEd25519` are distinct protocol
|
|
90
|
+
// classes with incompatible keyshare formats. Downstream `instanceof`
|
|
91
|
+
// dispatch (createWalletAccount / importPrivateKey / derive*) always tests
|
|
92
|
+
// `ExportableEd25519` before `Ed25519` — most-specific-first — so it stays
|
|
93
|
+
// correct even if `ExportableEd25519` were a subclass of `Ed25519`.
|
|
94
|
+
return variant === 'ed25519Standard' ? new Ed25519(baseRelayUrl) : new ExportableEd25519(baseRelayUrl);
|
|
88
95
|
case SigningAlgorithm.BIP340:
|
|
89
96
|
return new BIP340(baseRelayUrl);
|
|
90
97
|
case SigningAlgorithm.EDBLS12_377:
|
|
@@ -93,11 +100,12 @@ const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD
|
|
|
93
100
|
throw new Error(`Unsupported signing algorithm: ${signingAlgorithm}`);
|
|
94
101
|
}
|
|
95
102
|
};
|
|
96
|
-
const getMPCSigner = ({ chainName, baseRelayUrl, bitcoinConfig })=>{
|
|
103
|
+
const getMPCSigner = ({ chainName, baseRelayUrl, bitcoinConfig, variant })=>{
|
|
97
104
|
const chainConfig = getMPCChainConfig(chainName, bitcoinConfig);
|
|
98
105
|
const signatureScheme = getMPCSignatureScheme({
|
|
99
106
|
signingAlgorithm: chainConfig.signingAlgorithm,
|
|
100
|
-
baseRelayUrl
|
|
107
|
+
baseRelayUrl,
|
|
108
|
+
variant
|
|
101
109
|
});
|
|
102
110
|
return signatureScheme;
|
|
103
111
|
};
|
|
@@ -1487,6 +1495,37 @@ const NON_RETRYABLE_CEREMONY_ERROR_MESSAGES = [
|
|
|
1487
1495
|
ERROR_INCORRECT_PASSWORD,
|
|
1488
1496
|
ERROR_MULTIPLE_WALLETS_PER_CHAIN
|
|
1489
1497
|
];
|
|
1498
|
+
/**
|
|
1499
|
+
* True when the backend rejected a create because the user already has a wallet
|
|
1500
|
+
* on that chain — an expected business-rule outcome, not a system failure.
|
|
1501
|
+
*
|
|
1502
|
+
* The string never appears on `error.message`: the backend returns it in the 400
|
|
1503
|
+
* response body as `{ existingWalletId, error: '<this string>' }`, so an
|
|
1504
|
+
* `AxiosError` carries axios's own "Request failed with status code 400" message
|
|
1505
|
+
* and `handleAxiosError` maps the status to "Invalid request". `WalletApiError`
|
|
1506
|
+
* does preserve the body value, but on `code` (via `extractServerErrorCode`),
|
|
1507
|
+
* not `message`. A `.message` comparison therefore never matches, which is why
|
|
1508
|
+
* the intended downgrade at the keyGen catch site had never fired in production.
|
|
1509
|
+
*
|
|
1510
|
+
* `WalletApiError` is matched on `name` as well as `instanceof`, for the reason
|
|
1511
|
+
* documented on `getWalletApiErrorStatus` in services/logger.ts: core is inlined
|
|
1512
|
+
* into more than one bundle, so a second copy of the class defeats `instanceof`.
|
|
1513
|
+
* A foreign copy has no `response.data` and the generic "Invalid request"
|
|
1514
|
+
* message, so an instanceof-only check misses every branch below.
|
|
1515
|
+
*/ const isMultipleWalletsPerChainRejection = (error)=>{
|
|
1516
|
+
var _error_response;
|
|
1517
|
+
const isWalletApiError = error instanceof WalletApiError || error instanceof Error && error.name === 'WalletApiError';
|
|
1518
|
+
if (isWalletApiError && error.code === ERROR_MULTIPLE_WALLETS_PER_CHAIN) return true;
|
|
1519
|
+
// Raw AxiosError: the rejection reaches the keyGen catch before
|
|
1520
|
+
// `handleAxiosError` has converted it, so read the response body directly.
|
|
1521
|
+
const body = error == null ? void 0 : (_error_response = error.response) == null ? void 0 : _error_response.data;
|
|
1522
|
+
if (body && typeof body === 'object' && body['error'] === ERROR_MULTIPLE_WALLETS_PER_CHAIN) {
|
|
1523
|
+
return true;
|
|
1524
|
+
}
|
|
1525
|
+
// SSE transport assigns the body onto a plain Error.
|
|
1526
|
+
if ((error == null ? void 0 : error.error) === ERROR_MULTIPLE_WALLETS_PER_CHAIN) return true;
|
|
1527
|
+
return error instanceof Error && error.message === ERROR_MULTIPLE_WALLETS_PER_CHAIN;
|
|
1528
|
+
};
|
|
1490
1529
|
/**
|
|
1491
1530
|
* Marks an error as non-retryable in place so `retryPromise` will skip
|
|
1492
1531
|
* remaining attempts. Used at ceremony retry boundaries.
|
|
@@ -2282,14 +2321,33 @@ const classifyOne = (error)=>{
|
|
|
2282
2321
|
return fallback;
|
|
2283
2322
|
};
|
|
2284
2323
|
const MAX_DESCRIBED_LENGTH = 200;
|
|
2324
|
+
/**
|
|
2325
|
+
* Keys safe to echo when a thrown object exposes no recognised message field.
|
|
2326
|
+
* Deliberately an allowlist: the alternative — serialising the whole object —
|
|
2327
|
+
* cannot be made safe, because a thrown MPC signer rejection or API body may
|
|
2328
|
+
* carry key-share material, a raw transaction body or an auth token.
|
|
2329
|
+
*/ const SAFE_DETAIL_KEYS = [
|
|
2330
|
+
'status',
|
|
2331
|
+
'statusCode',
|
|
2332
|
+
'code',
|
|
2333
|
+
'error_code',
|
|
2334
|
+
'errorCode',
|
|
2335
|
+
'name',
|
|
2336
|
+
'type'
|
|
2337
|
+
];
|
|
2338
|
+
const isScalar = (value)=>typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
|
|
2285
2339
|
/**
|
|
2286
2340
|
* Builds a human-readable message for a thrown value that is not an `Error` —
|
|
2287
2341
|
* most often an API response body, e.g.
|
|
2288
2342
|
* `{ existingWalletId, error: 'Multiple wallets per chain not allowed' }`.
|
|
2289
2343
|
*
|
|
2290
2344
|
* `String(value)` would collapse that to `[object Object]`, losing the only
|
|
2291
|
-
* diagnostic content it had, so
|
|
2292
|
-
*
|
|
2345
|
+
* diagnostic content it had, so a recognised message field is preferred.
|
|
2346
|
+
*
|
|
2347
|
+
* Never serialises an unrecognised object wholesale. Where no message field
|
|
2348
|
+
* exists, only `SAFE_DETAIL_KEYS` scalars are echoed, and failing that a fixed
|
|
2349
|
+
* placeholder — so a value carrying secrets can never reach the logging backend
|
|
2350
|
+
* just because it lacked a `message`.
|
|
2293
2351
|
*/ const describeNonError = (value)=>{
|
|
2294
2352
|
if (typeof value === 'string') return value.slice(0, MAX_DESCRIBED_LENGTH);
|
|
2295
2353
|
if (value !== null && typeof value === 'object') {
|
|
@@ -2300,11 +2358,8 @@ const MAX_DESCRIBED_LENGTH = 200;
|
|
|
2300
2358
|
record['detail']
|
|
2301
2359
|
].find((candidate)=>typeof candidate === 'string' && candidate.length > 0);
|
|
2302
2360
|
if (named) return named.slice(0, MAX_DESCRIBED_LENGTH);
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
} catch (e) {
|
|
2306
|
-
// Circular or non-serialisable — fall through to String().
|
|
2307
|
-
}
|
|
2361
|
+
const safeDetails = SAFE_DETAIL_KEYS.filter((key)=>isScalar(record[key])).map((key)=>`${key}=${String(record[key])}`);
|
|
2362
|
+
return safeDetails.length > 0 ? safeDetails.join(' ').slice(0, MAX_DESCRIBED_LENGTH) : 'Non-Error value thrown with no recognised message field';
|
|
2308
2363
|
}
|
|
2309
2364
|
return String(value).slice(0, MAX_DESCRIBED_LENGTH);
|
|
2310
2365
|
};
|
|
@@ -2320,27 +2375,38 @@ const MAX_DESCRIBED_LENGTH = 200;
|
|
|
2320
2375
|
* under `operationError` instead, and a real `Error` is synthesised so the log
|
|
2321
2376
|
* still gets a stack and an `error.kind`.
|
|
2322
2377
|
*
|
|
2378
|
+
* A non-`Error` is recorded as its bounded `describeNonError` description only,
|
|
2379
|
+
* never the raw value — the same rule `logError` applies when it logs a cause as
|
|
2380
|
+
* `{ name, message }` — while `operationError` remains its own attribute so "the
|
|
2381
|
+
* thrown value was not an Error" stays queryable.
|
|
2382
|
+
*
|
|
2323
2383
|
* Returned as a plain object so this is unit-testable without constructing a
|
|
2324
2384
|
* wallet client (which pulls the MPC WASM bundle).
|
|
2325
2385
|
*/ const buildOperationFailureLog = (error)=>{
|
|
2326
2386
|
// Classify the original value: an Error's `cause` chain carries the severity,
|
|
2327
2387
|
// and normalising first would discard it.
|
|
2328
2388
|
const level = resolveLogLevel(error, 'error');
|
|
2329
|
-
|
|
2389
|
+
if (error instanceof Error) return {
|
|
2330
2390
|
level,
|
|
2331
2391
|
context: undefined,
|
|
2332
2392
|
error
|
|
2333
|
-
}
|
|
2393
|
+
};
|
|
2394
|
+
const described = describeNonError(error);
|
|
2395
|
+
return {
|
|
2334
2396
|
level,
|
|
2335
2397
|
context: {
|
|
2336
|
-
operationError:
|
|
2398
|
+
operationError: described
|
|
2337
2399
|
},
|
|
2338
|
-
error: new Error(
|
|
2400
|
+
error: new Error(described)
|
|
2339
2401
|
};
|
|
2340
2402
|
};
|
|
2341
2403
|
const logError = ({ message, error, context, level = 'error' })=>{
|
|
2342
2404
|
if (error instanceof AxiosError) {
|
|
2343
|
-
|
|
2405
|
+
// Forward the caller's severity hint. It used to be dropped here, so a call
|
|
2406
|
+
// site that knew its failure was expected still got logged at `error`.
|
|
2407
|
+
handleAxiosError(error, message, context, {
|
|
2408
|
+
level
|
|
2409
|
+
});
|
|
2344
2410
|
return;
|
|
2345
2411
|
}
|
|
2346
2412
|
const resolvedLevel = resolveLogLevel(error, level);
|
|
@@ -3378,28 +3444,34 @@ class DynamicWalletClient {
|
|
|
3378
3444
|
}, this.getTraceContext(traceContext))));
|
|
3379
3445
|
throw new Error('Timed out waiting for wallet creation ceremony to complete');
|
|
3380
3446
|
}
|
|
3381
|
-
async clientInitializeKeyGen({ chainName, thresholdSignatureScheme, bitcoinConfig }) {
|
|
3447
|
+
async clientInitializeKeyGen({ chainName, thresholdSignatureScheme, bitcoinConfig, variant }) {
|
|
3382
3448
|
// Get the mpc signer
|
|
3383
3449
|
const mpcSigner = getMPCSigner({
|
|
3384
3450
|
chainName,
|
|
3385
3451
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
3386
|
-
bitcoinConfig
|
|
3452
|
+
bitcoinConfig,
|
|
3453
|
+
variant
|
|
3387
3454
|
});
|
|
3388
3455
|
const clientThreshold = getClientThreshold(thresholdSignatureScheme);
|
|
3389
3456
|
const keygenInitResults = await Promise.all(Array(clientThreshold).fill(null).map(()=>mpcSigner.initKeygen()));
|
|
3390
3457
|
return keygenInitResults;
|
|
3391
3458
|
}
|
|
3392
|
-
async derivePublicKey({ chainName, keyShare, derivationPath, bitcoinConfig }) {
|
|
3459
|
+
async derivePublicKey({ chainName, keyShare, derivationPath, bitcoinConfig, variant }) {
|
|
3393
3460
|
const mpcSigner = getMPCSigner({
|
|
3394
3461
|
chainName,
|
|
3395
3462
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
3396
|
-
bitcoinConfig
|
|
3463
|
+
bitcoinConfig,
|
|
3464
|
+
variant
|
|
3397
3465
|
});
|
|
3398
3466
|
let publicKey;
|
|
3399
3467
|
if (mpcSigner instanceof Ecdsa) {
|
|
3400
3468
|
publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
|
|
3401
3469
|
} else if (mpcSigner instanceof ExportableEd25519) {
|
|
3402
3470
|
publicKey = await mpcSigner.getPubkey(keyShare);
|
|
3471
|
+
} else if (mpcSigner instanceof Ed25519) {
|
|
3472
|
+
// Non-exportable Ed25519 (raw-scalar-imported wallets). Derivation is not
|
|
3473
|
+
// standardized for ed25519, so the root pubkey is taken with an undefined path.
|
|
3474
|
+
publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
|
|
3403
3475
|
} else if (mpcSigner instanceof BIP340) {
|
|
3404
3476
|
publicKey = await mpcSigner.deriveTweakPubkey(keyShare, derivationPath);
|
|
3405
3477
|
}
|
|
@@ -3728,7 +3800,7 @@ class DynamicWalletClient {
|
|
|
3728
3800
|
// rejection (the user already has a wallet for this chain), not a system
|
|
3729
3801
|
// failure — log it at warn so it doesn't flood error dashboards. All
|
|
3730
3802
|
// other keygen failures stay at error.
|
|
3731
|
-
const isExpectedRejection = error
|
|
3803
|
+
const isExpectedRejection = isMultipleWalletsPerChainRejection(error);
|
|
3732
3804
|
// On the HTTP transport, tag the failure with the cross-repo join keys and
|
|
3733
3805
|
// a stable errorKind so it lines up with the redcoast worker ceremony logs.
|
|
3734
3806
|
const usedHttpTransport = this.featureFlags[FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT] === true && !!idempotencyKey;
|
|
@@ -3763,7 +3835,7 @@ class DynamicWalletClient {
|
|
|
3763
3835
|
shouldRetry: (error)=>!isNonRetryableCeremonyError(error)
|
|
3764
3836
|
});
|
|
3765
3837
|
}
|
|
3766
|
-
async runImportRawPrivateKeyAttempt({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, onError, onCeremonyComplete, traceContext, legacyWalletId, password, signedSessionId }) {
|
|
3838
|
+
async runImportRawPrivateKeyAttempt({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, onError, onCeremonyComplete, traceContext, legacyWalletId, password, signedSessionId, isRawScalarImport }) {
|
|
3767
3839
|
const dynamicRequestId = v4();
|
|
3768
3840
|
try {
|
|
3769
3841
|
this.assertPasswordRequired(password);
|
|
@@ -3771,15 +3843,21 @@ class DynamicWalletClient {
|
|
|
3771
3843
|
password,
|
|
3772
3844
|
signedSessionId
|
|
3773
3845
|
});
|
|
3846
|
+
// A raw-scalar import must run on the non-exportable `Ed25519` protocol for
|
|
3847
|
+
// the whole ceremony (init keygen, import, pubkey derivation) so the produced
|
|
3848
|
+
// key shares are consistent. Non-raw imports keep the default ('ed25519Exportable').
|
|
3849
|
+
const variant = isRawScalarImport ? 'ed25519Standard' : undefined;
|
|
3774
3850
|
const mpcSigner = getMPCSigner({
|
|
3775
3851
|
chainName,
|
|
3776
3852
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
3777
|
-
bitcoinConfig
|
|
3853
|
+
bitcoinConfig,
|
|
3854
|
+
variant
|
|
3778
3855
|
});
|
|
3779
3856
|
const clientKeygenInitResults = await this.clientInitializeKeyGen({
|
|
3780
3857
|
chainName,
|
|
3781
3858
|
thresholdSignatureScheme,
|
|
3782
|
-
bitcoinConfig
|
|
3859
|
+
bitcoinConfig,
|
|
3860
|
+
variant
|
|
3783
3861
|
});
|
|
3784
3862
|
const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
|
|
3785
3863
|
this.logger.info('[DynamicWaasWalletClient] Client key generation initialized', _extends({
|
|
@@ -3797,7 +3875,9 @@ class DynamicWalletClient {
|
|
|
3797
3875
|
bitcoinConfig,
|
|
3798
3876
|
onError,
|
|
3799
3877
|
onCeremonyComplete,
|
|
3800
|
-
legacyWalletId
|
|
3878
|
+
legacyWalletId,
|
|
3879
|
+
// Persist the protocol variant server-side so later sign/reshare read it back.
|
|
3880
|
+
variant
|
|
3801
3881
|
});
|
|
3802
3882
|
this.logger.info('[DynamicWaasWalletClient] Server key generation initialized', _extends({
|
|
3803
3883
|
roomId,
|
|
@@ -3815,7 +3895,9 @@ class DynamicWalletClient {
|
|
|
3815
3895
|
...serverKeygenIds,
|
|
3816
3896
|
...otherClientKeygenIds
|
|
3817
3897
|
];
|
|
3818
|
-
const importerKeygenResult =
|
|
3898
|
+
const importerKeygenResult = isRawScalarImport ? // uses the 32 bytes directly as the signing scalar instead of
|
|
3899
|
+
// SHA-512-expanding them as an RFC-8032 seed.
|
|
3900
|
+
await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds, true) : await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
|
|
3819
3901
|
return importerKeygenResult;
|
|
3820
3902
|
} else {
|
|
3821
3903
|
const recipientKeygenResult = await mpcSigner.importPrivateKeyRecipient(roomId, threshold, currentInit, [
|
|
@@ -3832,7 +3914,8 @@ class DynamicWalletClient {
|
|
|
3832
3914
|
chainName,
|
|
3833
3915
|
keyShare: clientKeygenResult,
|
|
3834
3916
|
derivationPath,
|
|
3835
|
-
bitcoinConfig
|
|
3917
|
+
bitcoinConfig,
|
|
3918
|
+
variant
|
|
3836
3919
|
});
|
|
3837
3920
|
this.logger.info('[DynamicWaasWalletClient] Completed import of raw private key', _extends({
|
|
3838
3921
|
rawPublicKey,
|
|
@@ -3867,6 +3950,8 @@ class DynamicWalletClient {
|
|
|
3867
3950
|
if (typeof message !== 'string') {
|
|
3868
3951
|
message = `0x${Buffer.from(message).toString('hex')}`;
|
|
3869
3952
|
}
|
|
3953
|
+
// Note: the server signing party (wallet-service) selects the ed25519
|
|
3954
|
+
// protocol variant from its own persisted EAC, so it is NOT threaded here.
|
|
3870
3955
|
const serializedContext = context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined;
|
|
3871
3956
|
const useHttpTransport = this.featureFlags[FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT] === true && this.featureFlags[FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT_SIGNMESSAGE] === true;
|
|
3872
3957
|
const params = {
|
|
@@ -3930,7 +4015,7 @@ class DynamicWalletClient {
|
|
|
3930
4015
|
}
|
|
3931
4016
|
return signatureBytes;
|
|
3932
4017
|
}
|
|
3933
|
-
async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted, traceContext, bitcoinConfig }) {
|
|
4018
|
+
async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted, traceContext, bitcoinConfig, variant }) {
|
|
3934
4019
|
try {
|
|
3935
4020
|
const chainConfig = getMPCChainConfig(chainName, bitcoinConfig);
|
|
3936
4021
|
const signingAlgo = chainConfig.signingAlgorithm;
|
|
@@ -3943,7 +4028,7 @@ class DynamicWalletClient {
|
|
|
3943
4028
|
});
|
|
3944
4029
|
const tweak = this.convertTweakForBIP340(bitcoinConfig == null ? void 0 : bitcoinConfig.tweak);
|
|
3945
4030
|
this.logger.info('Forward MPC enabled, signing message with forward MPC (new)', this.getTraceContext(traceContext));
|
|
3946
|
-
const { signature: signatureBytes } = await this.apiClient.forwardMPCClient.signMessage({
|
|
4031
|
+
const { signature: signatureBytes } = await this.apiClient.forwardMPCClient.signMessage(_extends({
|
|
3947
4032
|
keyshare: keyShare,
|
|
3948
4033
|
message: messageToSign,
|
|
3949
4034
|
relayDomain: this.baseMPCRelayApiUrl || '',
|
|
@@ -3955,7 +4040,9 @@ class DynamicWalletClient {
|
|
|
3955
4040
|
traceContext,
|
|
3956
4041
|
userId: this.userId,
|
|
3957
4042
|
environmentId: this.environmentId
|
|
3958
|
-
}
|
|
4043
|
+
}, variant === 'ed25519Standard' ? {
|
|
4044
|
+
ed25519Variant: 'standard'
|
|
4045
|
+
} : {}));
|
|
3959
4046
|
if (!(signatureBytes instanceof Uint8Array)) {
|
|
3960
4047
|
throw new TypeError(`Invalid signature format: expected Uint8Array, got ${typeof signatureBytes}`);
|
|
3961
4048
|
}
|
|
@@ -3975,15 +4062,22 @@ class DynamicWalletClient {
|
|
|
3975
4062
|
throw error;
|
|
3976
4063
|
}
|
|
3977
4064
|
}
|
|
3978
|
-
async clientSign({ chainName, message, roomId: initialRoomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext, bitcoinConfig, refreshRoom }) {
|
|
4065
|
+
async clientSign({ chainName, message, roomId: initialRoomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext, bitcoinConfig, variant, refreshRoom }) {
|
|
3979
4066
|
// Reassigned when forward MPC fails and we mint a fresh room for the
|
|
3980
4067
|
// relay-based fallback below.
|
|
3981
4068
|
let roomId = initialRoomId;
|
|
4069
|
+
// Non-exportable Ed25519 (raw-scalar import) derives its address at the
|
|
4070
|
+
// root — see derivePrivateKeyFromExport's identical guard. Unlike
|
|
4071
|
+
// ExportableEd25519 (which ignores this arg), Ed25519.sign() *applies* a
|
|
4072
|
+
// non-undefined path, so forwarding the wallet's stored derivation path
|
|
4073
|
+
// here would sign under a derived child key instead of the wallet's own.
|
|
4074
|
+
const effectiveDerivationPath = variant === 'ed25519Standard' ? undefined : derivationPath;
|
|
3982
4075
|
try {
|
|
3983
4076
|
const mpcSigner = getMPCSigner({
|
|
3984
4077
|
chainName,
|
|
3985
4078
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
3986
|
-
bitcoinConfig
|
|
4079
|
+
bitcoinConfig,
|
|
4080
|
+
variant
|
|
3987
4081
|
});
|
|
3988
4082
|
const formattedMessage = isFormatted ? new MessageHash(message) : formatMessage(chainName, message);
|
|
3989
4083
|
this.logger.debug('[DynamicWaasWalletClient] Starting client sign', _extends({
|
|
@@ -4007,12 +4101,13 @@ class DynamicWalletClient {
|
|
|
4007
4101
|
message,
|
|
4008
4102
|
roomId,
|
|
4009
4103
|
keyShare,
|
|
4010
|
-
derivationPath,
|
|
4104
|
+
derivationPath: effectiveDerivationPath,
|
|
4011
4105
|
formattedMessage,
|
|
4012
4106
|
dynamicRequestId,
|
|
4013
4107
|
isFormatted,
|
|
4014
4108
|
traceContext,
|
|
4015
|
-
bitcoinConfig
|
|
4109
|
+
bitcoinConfig,
|
|
4110
|
+
variant
|
|
4016
4111
|
});
|
|
4017
4112
|
} catch (error) {
|
|
4018
4113
|
const errorInfo = classifyForwardMpcError(error);
|
|
@@ -4056,7 +4151,7 @@ class DynamicWalletClient {
|
|
|
4056
4151
|
if (!('sign' in mpcSigner)) {
|
|
4057
4152
|
throw new TypeError(`Message signing is not supported for chain ${chainName} — use signTransaction instead`);
|
|
4058
4153
|
}
|
|
4059
|
-
const signature = await mpcSigner.sign(roomId, keyShare, messageToSign,
|
|
4154
|
+
const signature = await mpcSigner.sign(roomId, keyShare, messageToSign, effectiveDerivationPath, tweak);
|
|
4060
4155
|
return signature;
|
|
4061
4156
|
} catch (error) {
|
|
4062
4157
|
logError({
|
|
@@ -4140,7 +4235,8 @@ class DynamicWalletClient {
|
|
|
4140
4235
|
chainName,
|
|
4141
4236
|
localShares,
|
|
4142
4237
|
recordedKeygenIds,
|
|
4143
|
-
bitcoinConfig
|
|
4238
|
+
bitcoinConfig,
|
|
4239
|
+
variant: this.ed25519VariantForWallet(accountAddress)
|
|
4144
4240
|
});
|
|
4145
4241
|
localKeygenIds = comparison.localKeygenIds;
|
|
4146
4242
|
staleness = comparison.fresh ? 'fresh' : 'stale';
|
|
@@ -4206,11 +4302,14 @@ class DynamicWalletClient {
|
|
|
4206
4302
|
* the server recorded for the wallet's Dynamic backups. Shared by the
|
|
4207
4303
|
* stale-share heal (verifyAndRecoverStaleShare) and the backup preflight
|
|
4208
4304
|
* (ensureLocalSharesAreFresh).
|
|
4209
|
-
*/ async compareShareGenerations({ accountAddress, chainName, localShares, recordedKeygenIds, bitcoinConfig }) {
|
|
4305
|
+
*/ async compareShareGenerations({ accountAddress, chainName, localShares, recordedKeygenIds, bitcoinConfig, variant }) {
|
|
4210
4306
|
const mpcSigner = getMPCSigner({
|
|
4211
4307
|
chainName,
|
|
4212
4308
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
4213
|
-
bitcoinConfig: bitcoinConfig != null ? bitcoinConfig : this.getBitcoinConfigForChain(chainName, accountAddress)
|
|
4309
|
+
bitcoinConfig: bitcoinConfig != null ? bitcoinConfig : this.getBitcoinConfigForChain(chainName, accountAddress),
|
|
4310
|
+
// Raw-scalar (non-exportable Ed25519) wallets: build the matching signer so
|
|
4311
|
+
// getKeygenIdForShare deserializes the Ed25519 share (else "Invalid discriminant").
|
|
4312
|
+
variant
|
|
4214
4313
|
});
|
|
4215
4314
|
const localKeygenIds = await Promise.all(localShares.map((share)=>this.getKeygenIdForShare(mpcSigner, share)));
|
|
4216
4315
|
const recordedSet = new Set(recordedKeygenIds);
|
|
@@ -4368,6 +4467,7 @@ class DynamicWalletClient {
|
|
|
4368
4467
|
dynamicRequestId,
|
|
4369
4468
|
traceContext,
|
|
4370
4469
|
bitcoinConfig,
|
|
4470
|
+
variant: wallet.variant,
|
|
4371
4471
|
// On forward-MPC fallback, re-run the server sign with no roomId so a
|
|
4372
4472
|
// brand-new server-joined room is created for the relay ceremony.
|
|
4373
4473
|
refreshRoom: async ()=>{
|
|
@@ -4667,7 +4767,11 @@ class DynamicWalletClient {
|
|
|
4667
4767
|
const mpcSigner = getMPCSigner({
|
|
4668
4768
|
chainName,
|
|
4669
4769
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
4670
|
-
bitcoinConfig
|
|
4770
|
+
bitcoinConfig,
|
|
4771
|
+
// Raw-scalar (non-exportable Ed25519) wallets must refresh on the same
|
|
4772
|
+
// protocol their shares were created with, or the ceremony fails to
|
|
4773
|
+
// deserialize them ("Invalid discriminant").
|
|
4774
|
+
variant: this.ed25519VariantForWallet(accountAddress)
|
|
4671
4775
|
});
|
|
4672
4776
|
// Ensure client key shares exist before hitting the API
|
|
4673
4777
|
const clientKeyShares = await this.ensureClientShare(accountAddress);
|
|
@@ -4849,11 +4953,26 @@ class DynamicWalletClient {
|
|
|
4849
4953
|
}
|
|
4850
4954
|
return mpcSigner.exportID(share);
|
|
4851
4955
|
}
|
|
4852
|
-
|
|
4956
|
+
/**
|
|
4957
|
+
* The ed25519 protocol variant for an existing wallet, read from the wallet map
|
|
4958
|
+
* (sourced from `walletProperties.settings.variant`, persisted at import).
|
|
4959
|
+
*
|
|
4960
|
+
* It is per-wallet protocol metadata — like `thresholdSignatureScheme` or
|
|
4961
|
+
* `addressType` — and the single source of truth for which signer to build when
|
|
4962
|
+
* operating on a wallet's key shares. Wallets imported from a raw ed25519 scalar
|
|
4963
|
+
* (e.g. Fireblocks Embedded Wallets) are `'ed25519Standard'` (non-exportable `Ed25519`);
|
|
4964
|
+
* everything else is `undefined` → the default exportable protocol. Using the
|
|
4965
|
+
* wrong one fails to deserialize the key share with "Invalid discriminant".
|
|
4966
|
+
*/ ed25519VariantForWallet(accountAddress) {
|
|
4967
|
+
var _this_getWalletFromMap;
|
|
4968
|
+
return (_this_getWalletFromMap = this.getWalletFromMap(accountAddress)) == null ? void 0 : _this_getWalletFromMap.variant;
|
|
4969
|
+
}
|
|
4970
|
+
async getExportId({ chainName, clientKeyShare, bitcoinConfig, accountAddress }) {
|
|
4853
4971
|
const mpcSigner = getMPCSigner({
|
|
4854
4972
|
chainName,
|
|
4855
4973
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
4856
|
-
bitcoinConfig
|
|
4974
|
+
bitcoinConfig,
|
|
4975
|
+
variant: accountAddress ? this.ed25519VariantForWallet(accountAddress) : undefined
|
|
4857
4976
|
});
|
|
4858
4977
|
try {
|
|
4859
4978
|
const exportId = await this.getKeygenIdForShare(mpcSigner, clientKeyShare);
|
|
@@ -4901,7 +5020,8 @@ class DynamicWalletClient {
|
|
|
4901
5020
|
return this.getExportId({
|
|
4902
5021
|
chainName,
|
|
4903
5022
|
clientKeyShare,
|
|
4904
|
-
bitcoinConfig
|
|
5023
|
+
bitcoinConfig,
|
|
5024
|
+
accountAddress
|
|
4905
5025
|
});
|
|
4906
5026
|
}
|
|
4907
5027
|
/**
|
|
@@ -4942,7 +5062,8 @@ class DynamicWalletClient {
|
|
|
4942
5062
|
const existingClientKeygenIds = await Promise.all(existingClientKeyShares.map(async (keyShare)=>await this.getExportId({
|
|
4943
5063
|
chainName,
|
|
4944
5064
|
clientKeyShare: keyShare,
|
|
4945
|
-
bitcoinConfig
|
|
5065
|
+
bitcoinConfig,
|
|
5066
|
+
accountAddress
|
|
4946
5067
|
})));
|
|
4947
5068
|
return {
|
|
4948
5069
|
newClientInitKeygenResults,
|
|
@@ -5011,7 +5132,8 @@ class DynamicWalletClient {
|
|
|
5011
5132
|
const existingClientKeygenId = await this.getExportId({
|
|
5012
5133
|
chainName,
|
|
5013
5134
|
clientKeyShare: existingClientShare,
|
|
5014
|
-
bitcoinConfig
|
|
5135
|
+
bitcoinConfig,
|
|
5136
|
+
accountAddress
|
|
5015
5137
|
});
|
|
5016
5138
|
// Gate matches internalRefresh/Reshare — SSE error rejects awaitTerminal.
|
|
5017
5139
|
let newShareSetId;
|
|
@@ -5620,7 +5742,11 @@ class DynamicWalletClient {
|
|
|
5620
5742
|
const mpcSigner = getMPCSigner({
|
|
5621
5743
|
chainName,
|
|
5622
5744
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
5623
|
-
bitcoinConfig
|
|
5745
|
+
bitcoinConfig,
|
|
5746
|
+
// Raw-scalar (non-exportable Ed25519) wallets must reshare on the same
|
|
5747
|
+
// protocol their shares were created with, or the ceremony fails to
|
|
5748
|
+
// deserialize them ("Invalid discriminant").
|
|
5749
|
+
variant: this.ed25519VariantForWallet(accountAddress)
|
|
5624
5750
|
});
|
|
5625
5751
|
let existingReshareResults;
|
|
5626
5752
|
let newReshareResults;
|
|
@@ -5984,6 +6110,9 @@ class DynamicWalletClient {
|
|
|
5984
6110
|
if (mpcSigner instanceof ExportableEd25519) {
|
|
5985
6111
|
return new ExportableEd25519KeygenResult(extractedPubkey, secretShare);
|
|
5986
6112
|
}
|
|
6113
|
+
if (mpcSigner instanceof Ed25519) {
|
|
6114
|
+
return new Ed25519KeygenResult(extractedPubkey, secretShare);
|
|
6115
|
+
}
|
|
5987
6116
|
return new BIP340KeygenResult(extractedPubkey, secretShare);
|
|
5988
6117
|
}
|
|
5989
6118
|
async exportKey({ accountAddress, chainName, bitcoinConfig, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, traceContext }) {
|
|
@@ -6014,13 +6143,15 @@ class DynamicWalletClient {
|
|
|
6014
6143
|
const mpcSigner = getMPCSigner({
|
|
6015
6144
|
chainName,
|
|
6016
6145
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
6017
|
-
bitcoinConfig
|
|
6146
|
+
bitcoinConfig,
|
|
6147
|
+
variant: wallet.variant
|
|
6018
6148
|
});
|
|
6019
6149
|
const reconstructedKeyShare = await this.getReconstructedKeyShare(accountAddress, mpcSigner);
|
|
6020
6150
|
const exportId = await this.getExportId({
|
|
6021
6151
|
chainName,
|
|
6022
6152
|
clientKeyShare: reconstructedKeyShare,
|
|
6023
|
-
bitcoinConfig
|
|
6153
|
+
bitcoinConfig,
|
|
6154
|
+
accountAddress: wallet.accountAddress
|
|
6024
6155
|
});
|
|
6025
6156
|
const data = await this.apiClient.exportKey({
|
|
6026
6157
|
walletId: wallet.walletId,
|
|
@@ -6163,20 +6294,35 @@ class DynamicWalletClient {
|
|
|
6163
6294
|
if (mpcSigner instanceof Ecdsa) {
|
|
6164
6295
|
return mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
|
|
6165
6296
|
} else if (mpcSigner instanceof ExportableEd25519) {
|
|
6297
|
+
// ExportableEd25519's exportFullPrivateKey already returns the RFC-8032
|
|
6298
|
+
// form (hex of seed||pubkey); pass it straight through.
|
|
6166
6299
|
return keyExportRaw;
|
|
6300
|
+
} else if (mpcSigner instanceof Ed25519) {
|
|
6301
|
+
// Non-exportable Ed25519's exportFullPrivateKey returns an `spriv`
|
|
6302
|
+
// (extended private key), NOT a raw hex key — it must be parsed to the
|
|
6303
|
+
// actual private scalar. Returning the spriv verbatim makes the chain
|
|
6304
|
+
// client hex-decode it into an empty buffer (blank export field).
|
|
6305
|
+
//
|
|
6306
|
+
// This protocol is only used for raw-scalar import, whose public key is
|
|
6307
|
+
// the ROOT scalar (`A = s·B`, derived at import via derivePubkey(_, undefined)).
|
|
6308
|
+
// The chain-config derivation path stored on the wallet (e.g. Solana's
|
|
6309
|
+
// [44,501,0,0,0]) must NOT be applied here — deriving a child scalar would
|
|
6310
|
+
// export a key that doesn't correspond to the wallet's address.
|
|
6311
|
+
return mpcSigner.derivePrivateKeyFromSpriv(keyExportRaw);
|
|
6167
6312
|
} else if (mpcSigner instanceof BIP340) {
|
|
6168
6313
|
return mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
|
|
6169
6314
|
}
|
|
6170
6315
|
return undefined;
|
|
6171
6316
|
}
|
|
6172
|
-
async offlineExportKey({ chainName, keyShares, derivationPath }) {
|
|
6317
|
+
async offlineExportKey({ chainName, keyShares, derivationPath, variant }) {
|
|
6173
6318
|
try {
|
|
6174
6319
|
if (!keyShares || keyShares.length < 2) {
|
|
6175
6320
|
throw new Error(`Must provide at least min threshold of key shares`);
|
|
6176
6321
|
}
|
|
6177
6322
|
const mpcSigner = getMPCSigner({
|
|
6178
6323
|
chainName,
|
|
6179
|
-
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
6324
|
+
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
6325
|
+
variant
|
|
6180
6326
|
});
|
|
6181
6327
|
const walletKeyShares = keyShares.map((keyShare)=>{
|
|
6182
6328
|
if (!('pubkey' in keyShare)) {
|
|
@@ -6195,6 +6341,10 @@ class DynamicWalletClient {
|
|
|
6195
6341
|
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
|
|
6196
6342
|
} else if (mpcSigner instanceof ExportableEd25519) {
|
|
6197
6343
|
derivedPrivateKey = keyExportRaw;
|
|
6344
|
+
} else if (mpcSigner instanceof Ed25519) {
|
|
6345
|
+
// Non-exportable Ed25519 (raw-scalar) returns an `spriv`, not raw hex —
|
|
6346
|
+
// parse it to the root scalar (see derivePrivateKeyFromExport).
|
|
6347
|
+
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromSpriv(keyExportRaw);
|
|
6198
6348
|
} else if (mpcSigner instanceof BIP340) {
|
|
6199
6349
|
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
|
|
6200
6350
|
}
|
|
@@ -6400,7 +6550,7 @@ class DynamicWalletClient {
|
|
|
6400
6550
|
* @param thresholdSignatureScheme - The TSS scheme used
|
|
6401
6551
|
* @param derivationPath - Optional derivation path (will be computed from chainConfig if not provided)
|
|
6402
6552
|
* @param additionalProps - Any chain-specific additional properties to merge
|
|
6403
|
-
*/ initializeWalletMapEntry({ accountAddress, walletId, chainName, thresholdSignatureScheme, derivationPath, shareSetId, shareSetType, otherShareSets, additionalProps = {} }) {
|
|
6553
|
+
*/ initializeWalletMapEntry({ accountAddress, walletId, chainName, thresholdSignatureScheme, derivationPath, shareSetId, shareSetType, otherShareSets, variant, additionalProps = {} }) {
|
|
6404
6554
|
this.updateWalletMap(accountAddress, _extends({
|
|
6405
6555
|
accountAddress,
|
|
6406
6556
|
walletId,
|
|
@@ -6414,6 +6564,8 @@ class DynamicWalletClient {
|
|
|
6414
6564
|
shareSetType
|
|
6415
6565
|
} : {}, otherShareSets ? {
|
|
6416
6566
|
otherShareSets
|
|
6567
|
+
} : {}, variant ? {
|
|
6568
|
+
variant
|
|
6417
6569
|
} : {}, {
|
|
6418
6570
|
clientKeySharesBackupInfo: getClientKeyShareBackupInfo()
|
|
6419
6571
|
}, additionalProps));
|
|
@@ -6633,7 +6785,7 @@ class DynamicWalletClient {
|
|
|
6633
6785
|
tokenSource: googleDriveTokenSource
|
|
6634
6786
|
} : {});
|
|
6635
6787
|
}
|
|
6636
|
-
async publishDelegatedShare({ walletId, shareSetId, delegatedShare, signedSessionId, dynamicRequestId, chainName, bitcoinConfig, isPasswordEncrypted }) {
|
|
6788
|
+
async publishDelegatedShare({ walletId, shareSetId, delegatedShare, signedSessionId, dynamicRequestId, chainName, accountAddress, bitcoinConfig, isPasswordEncrypted }) {
|
|
6637
6789
|
var _publicKey_key, _publicKey_key1, _publicKey_key2;
|
|
6638
6790
|
const publicKey = await this.apiClient.getDelegatedEncryptionKey({
|
|
6639
6791
|
environmentId: this.environmentId
|
|
@@ -6658,7 +6810,8 @@ class DynamicWalletClient {
|
|
|
6658
6810
|
chainName,
|
|
6659
6811
|
clientKeyShare: delegatedShare,
|
|
6660
6812
|
bitcoinConfig,
|
|
6661
|
-
walletId
|
|
6813
|
+
walletId,
|
|
6814
|
+
accountAddress
|
|
6662
6815
|
});
|
|
6663
6816
|
return {
|
|
6664
6817
|
location: BackupLocation.DELEGATED,
|
|
@@ -6824,6 +6977,7 @@ class DynamicWalletClient {
|
|
|
6824
6977
|
signedSessionId: resolvedSignedSessionId,
|
|
6825
6978
|
dynamicRequestId,
|
|
6826
6979
|
chainName: walletData.chainName,
|
|
6980
|
+
accountAddress,
|
|
6827
6981
|
bitcoinConfig,
|
|
6828
6982
|
isPasswordEncrypted
|
|
6829
6983
|
}), {
|
|
@@ -6992,7 +7146,8 @@ class DynamicWalletClient {
|
|
|
6992
7146
|
accountAddress,
|
|
6993
7147
|
chainName,
|
|
6994
7148
|
localShares,
|
|
6995
|
-
recordedKeygenIds
|
|
7149
|
+
recordedKeygenIds,
|
|
7150
|
+
variant: walletData.variant
|
|
6996
7151
|
});
|
|
6997
7152
|
if (fresh) return;
|
|
6998
7153
|
this.logger.warn('[storeEncryptedBackupByWallet] Stale local shares detected; refreshing local storage', {
|
|
@@ -8417,9 +8572,17 @@ class DynamicWalletClient {
|
|
|
8417
8572
|
const user = await this.getUserWithEnvCheck(dynamicRequestId);
|
|
8418
8573
|
const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
|
|
8419
8574
|
for (const vc of waasWallets != null ? waasWallets : []){
|
|
8575
|
+
var _props_settings, _this_getWalletFromMap;
|
|
8420
8576
|
const addr = vc.address;
|
|
8421
8577
|
const props = vc.walletProperties;
|
|
8422
|
-
|
|
8578
|
+
var _props_settings_variant, _ref;
|
|
8579
|
+
// Resolve the ed25519 protocol variant: server-surfaced (walletProperties
|
|
8580
|
+
// .settings or top-level) first, then any locally-seeded value (set at raw
|
|
8581
|
+
// import). Required on a fresh session (no local seed) so recover/sign
|
|
8582
|
+
// rebuild the non-exportable Ed25519 signer for raw-scalar wallets instead
|
|
8583
|
+
// of defaulting to exportable → "Invalid discriminant".
|
|
8584
|
+
const resolvedVariant = (_ref = (_props_settings_variant = props == null ? void 0 : (_props_settings = props.settings) == null ? void 0 : _props_settings.variant) != null ? _props_settings_variant : props == null ? void 0 : props.variant) != null ? _ref : (_this_getWalletFromMap = this.getWalletFromMap(addr)) == null ? void 0 : _this_getWalletFromMap.variant;
|
|
8585
|
+
this.updateWalletMap(addr, _extends({
|
|
8423
8586
|
walletId: vc.id,
|
|
8424
8587
|
chainName: verifiedCredentialNameToChainEnum[vc.chain],
|
|
8425
8588
|
accountAddress: addr,
|
|
@@ -8432,7 +8595,9 @@ class DynamicWalletClient {
|
|
|
8432
8595
|
shareSetId: props == null ? void 0 : props.shareSetId,
|
|
8433
8596
|
shareSetType: props == null ? void 0 : props.shareSetType,
|
|
8434
8597
|
otherShareSets: props == null ? void 0 : props.otherShareSets
|
|
8435
|
-
}
|
|
8598
|
+
}, resolvedVariant ? {
|
|
8599
|
+
variant: resolvedVariant
|
|
8600
|
+
} : {}));
|
|
8436
8601
|
}
|
|
8437
8602
|
if (walletOperation !== WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
|
|
8438
8603
|
accountAddress,
|
|
@@ -8810,8 +8975,8 @@ class DynamicWalletClient {
|
|
|
8810
8975
|
this.userId = user.id;
|
|
8811
8976
|
const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
|
|
8812
8977
|
const wallets = waasWallets.map((vc)=>{
|
|
8813
|
-
var _this_getWalletFromMap, _vc_walletProperties, _vc_walletProperties1, _vc_walletProperties2, _vc_walletProperties3, _vc_walletProperties4, _vc_walletProperties5, _vc_walletProperties6;
|
|
8814
|
-
var _this_getWalletFromMap_derivationPath;
|
|
8978
|
+
var _this_getWalletFromMap, _vc_walletProperties, _vc_walletProperties1, _vc_walletProperties2, _vc_walletProperties3, _vc_walletProperties4, _vc_walletProperties5, _vc_walletProperties6, _vc_walletProperties_settings, _vc_walletProperties7, _vc_walletProperties8, _this_getWalletFromMap1;
|
|
8979
|
+
var _this_getWalletFromMap_derivationPath, _vc_walletProperties_settings_variant, _ref;
|
|
8815
8980
|
return {
|
|
8816
8981
|
walletId: vc.id,
|
|
8817
8982
|
chainName: verifiedCredentialNameToChainEnum[vc.chain],
|
|
@@ -8829,14 +8994,18 @@ class DynamicWalletClient {
|
|
|
8829
8994
|
// On-sign-on settings (shouldRefreshOnNextSignOn / reshareOnNextSignOn /
|
|
8830
8995
|
// revokeOnNextSignOn). Surfaced so the SDK can self-drive the on-sign-on
|
|
8831
8996
|
// orchestration instead of the host reading these flags.
|
|
8832
|
-
settings: (_vc_walletProperties6 = vc.walletProperties) == null ? void 0 : _vc_walletProperties6.settings
|
|
8997
|
+
settings: (_vc_walletProperties6 = vc.walletProperties) == null ? void 0 : _vc_walletProperties6.settings,
|
|
8998
|
+
// Prefer the server-persisted variant (surfaced via walletProperties.settings),
|
|
8999
|
+
// then the locally-known value (set at raw import) so same-session
|
|
9000
|
+
// import → sign works before the server has surfaced it.
|
|
9001
|
+
variant: (_ref = (_vc_walletProperties_settings_variant = (_vc_walletProperties7 = vc.walletProperties) == null ? void 0 : (_vc_walletProperties_settings = _vc_walletProperties7.settings) == null ? void 0 : _vc_walletProperties_settings.variant) != null ? _vc_walletProperties_settings_variant : (_vc_walletProperties8 = vc.walletProperties) == null ? void 0 : _vc_walletProperties8.variant) != null ? _ref : (_this_getWalletFromMap1 = this.getWalletFromMap(vc.address)) == null ? void 0 : _this_getWalletFromMap1.variant
|
|
8833
9002
|
};
|
|
8834
9003
|
});
|
|
8835
9004
|
const existingWalletMap = this.walletMap;
|
|
8836
9005
|
this.walletMap = wallets.reduce((acc, wallet)=>{
|
|
8837
9006
|
const normalizedAddress = normalizeAddress(wallet.accountAddress);
|
|
8838
9007
|
const existingWallet = existingWalletMap[normalizedAddress];
|
|
8839
|
-
acc[normalizedAddress] = {
|
|
9008
|
+
acc[normalizedAddress] = _extends({
|
|
8840
9009
|
walletId: wallet.walletId,
|
|
8841
9010
|
chainName: wallet.chainName,
|
|
8842
9011
|
accountAddress: wallet.accountAddress,
|
|
@@ -8849,7 +9018,9 @@ class DynamicWalletClient {
|
|
|
8849
9018
|
shareSetType: wallet.shareSetType,
|
|
8850
9019
|
businessAccountId: wallet.businessAccountId,
|
|
8851
9020
|
otherShareSets: wallet.otherShareSets
|
|
8852
|
-
}
|
|
9021
|
+
}, wallet.variant ? {
|
|
9022
|
+
variant: wallet.variant
|
|
9023
|
+
} : {});
|
|
8853
9024
|
return acc;
|
|
8854
9025
|
}, {});
|
|
8855
9026
|
this.logger.info('[walletMap] getWallets: walletMap updated from API', {
|
|
@@ -9303,4 +9474,44 @@ DynamicWalletClient.roomsPersistDirty = false;
|
|
|
9303
9474
|
// rooms back into the new user's storage (TOCTOU on the create→merge gap).
|
|
9304
9475
|
DynamicWalletClient.roomsGeneration = 0;
|
|
9305
9476
|
|
|
9306
|
-
|
|
9477
|
+
/**
|
|
9478
|
+
* Derive the 32-byte ed25519 public key from a **raw** 32-byte signing scalar
|
|
9479
|
+
* (`A = s·B`).
|
|
9480
|
+
*
|
|
9481
|
+
* This is NOT the same as deriving a public key from an RFC-8032 seed: the
|
|
9482
|
+
* standard ed25519 key-generation step takes a 32-byte seed and SHA-512-expands
|
|
9483
|
+
* it (then clamps) to obtain the scalar. A scalar exported from an external MPC
|
|
9484
|
+
* system (e.g. Fireblocks Embedded Wallets) has no preimage seed, so it must be
|
|
9485
|
+
* used directly as the scalar. Using `Keypair.fromSecretKey` / `keyPairFromSeed`
|
|
9486
|
+
* on such bytes would re-expand them and produce the wrong public key/address.
|
|
9487
|
+
*
|
|
9488
|
+
* @param scalar Raw 32-byte ed25519 scalar, little-endian, as hex (with or
|
|
9489
|
+
* without a `0x` prefix) or as raw bytes.
|
|
9490
|
+
* @returns The compressed 32-byte ed25519 public key.
|
|
9491
|
+
*/ const getEd25519PublicKeyFromRawScalar = (scalar)=>{
|
|
9492
|
+
let bytes;
|
|
9493
|
+
if (typeof scalar === 'string') {
|
|
9494
|
+
const hex = scalar.replace(/^0x/, '');
|
|
9495
|
+
// Validate before hexToBytes so malformed input fails with a clear, own
|
|
9496
|
+
// error rather than an opaque/implementation-specific one from the decoder.
|
|
9497
|
+
if (!/^[0-9a-fA-F]+$/.test(hex)) {
|
|
9498
|
+
throw new Error('Invalid raw ed25519 scalar: expected a hex string');
|
|
9499
|
+
}
|
|
9500
|
+
bytes = hexToBytes(hex);
|
|
9501
|
+
} else {
|
|
9502
|
+
bytes = scalar;
|
|
9503
|
+
}
|
|
9504
|
+
if (bytes.length !== 32) {
|
|
9505
|
+
throw new Error(`Invalid raw ed25519 scalar length: ${bytes.length}, expected 32`);
|
|
9506
|
+
}
|
|
9507
|
+
// Interpret the scalar little-endian and reduce mod the curve order. We do NOT
|
|
9508
|
+
// clamp here: the caller provides the actual signing scalar `s`, and the public
|
|
9509
|
+
// key is exactly `s·B`.
|
|
9510
|
+
const reduced = bytesToNumberLE(bytes) % ed25519.CURVE.n;
|
|
9511
|
+
if (reduced === 0n) {
|
|
9512
|
+
throw new Error('Invalid raw ed25519 scalar: reduces to zero mod the curve order');
|
|
9513
|
+
}
|
|
9514
|
+
return ed25519.ExtendedPoint.BASE.multiply(reduced).toRawBytes();
|
|
9515
|
+
};
|
|
9516
|
+
|
|
9517
|
+
export { BACKUP_FILENAME, CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX, DynamicWalletClient, ENVIRONMENT_SETTINGS_STORAGE_KEY, ERROR_ACCOUNT_ADDRESS_REQUIRED, ERROR_CREATE_WALLET_ACCOUNT, ERROR_EXPORT_PRIVATE_KEY, ERROR_IMPORT_PRIVATE_KEY, ERROR_INCORRECT_PASSWORD, ERROR_KEYGEN_FAILED, ERROR_MULTIPLE_WALLETS_PER_CHAIN, ERROR_NO_KEY_SHARES_BACKED_UP, ERROR_PASSWORD_MISMATCH, ERROR_PASSWORD_REQUIRED, ERROR_PASSWORD_REQUIRED_FOR_ENCRYPTED_WALLET, ERROR_PUBLIC_KEY_MISMATCH, ERROR_REFRESH_NOT_SUPPORTED_FOR_TWO_OF_THREE, ERROR_SIGN_MESSAGE, ERROR_SIGN_TYPED_DATA, ERROR_VERIFY_MESSAGE_SIGNATURE, ERROR_VERIFY_TRANSACTION_SIGNATURE, HEAVY_QUEUE_OPERATIONS, OperationSource, RECOVER_QUEUE_OPERATIONS, ROOM_CACHE_COUNT, ROOM_EXPIRATION_TIME, SIGNED_SESSION_ID_MIN_VERSION, SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION_BY_NAMESPACE, SIGN_QUEUE_OPERATIONS, STORAGE_KEY, StaleLocalSharesError, WalletBusyError, WalletNotReadyError, cancelICloudAuth, createAddCloudProviderToExistingDelegationDistribution, createBackupData, createCloudProviderDistribution, createDelegationOnlyDistribution, createDelegationWithCloudProviderDistribution, createDynamicOnlyDistribution, deleteICloudBackup, downloadStringAsFile, extractPubkey, formatEvmMessage, formatMessage, formatTronMessage, getActiveCloudProviders, getBitcoinAddressTypeFromAddress, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getDelegatedShareSet, getEd25519PublicKeyFromRawScalar, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isMultipleWalletsPerChainRejection, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isRoomIdAlreadyUsedError, isSignQueueOperation, isStaleClientSharesError, listICloudBackups, markCeremonyErrorNonRetryable, readEnvironmentSettings, resolveLogLevel, retryPromise, shouldReshareToSameBackups, timeoutPromise };
|