@dynamic-labs-wallet/browser 1.0.92 → 1.0.93

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 CHANGED
@@ -9,6 +9,8 @@ var loadArgon2idWasm = require('argon2id');
9
9
  var axios = require('axios');
10
10
  var gte = require('semver/functions/gte');
11
11
  var PQueue = require('p-queue');
12
+ var ed25519 = require('@noble/curves/ed25519');
13
+ var utils = require('@noble/curves/abstract/utils');
12
14
 
13
15
  function _extends() {
14
16
  _extends = Object.assign || function assign(target) {
@@ -78,12 +80,17 @@ function _extends() {
78
80
  return undefined;
79
81
  }
80
82
  };
81
- const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = core.MPC_RELAY_PROD_API_URL })=>{
83
+ const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = core.MPC_RELAY_PROD_API_URL, variant = core.DEFAULT_ED25519_VARIANT })=>{
82
84
  switch(signingAlgorithm){
83
85
  case primitives.SigningAlgorithm.ECDSA:
84
86
  return new web.Ecdsa(baseRelayUrl);
85
87
  case primitives.SigningAlgorithm.ED25519:
86
- return new web.ExportableEd25519(baseRelayUrl);
88
+ // `Ed25519` (non-exportable) and `ExportableEd25519` are distinct protocol
89
+ // classes with incompatible keyshare formats. Downstream `instanceof`
90
+ // dispatch (createWalletAccount / importPrivateKey / derive*) always tests
91
+ // `ExportableEd25519` before `Ed25519` — most-specific-first — so it stays
92
+ // correct even if `ExportableEd25519` were a subclass of `Ed25519`.
93
+ return variant === 'ed25519Standard' ? new web.Ed25519(baseRelayUrl) : new web.ExportableEd25519(baseRelayUrl);
87
94
  case primitives.SigningAlgorithm.BIP340:
88
95
  return new web.BIP340(baseRelayUrl);
89
96
  case primitives.SigningAlgorithm.EDBLS12_377:
@@ -92,11 +99,12 @@ const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = core.MPC_RELAY
92
99
  throw new Error(`Unsupported signing algorithm: ${signingAlgorithm}`);
93
100
  }
94
101
  };
95
- const getMPCSigner = ({ chainName, baseRelayUrl, bitcoinConfig })=>{
102
+ const getMPCSigner = ({ chainName, baseRelayUrl, bitcoinConfig, variant })=>{
96
103
  const chainConfig = core.getMPCChainConfig(chainName, bitcoinConfig);
97
104
  const signatureScheme = getMPCSignatureScheme({
98
105
  signingAlgorithm: chainConfig.signingAlgorithm,
99
- baseRelayUrl
106
+ baseRelayUrl,
107
+ variant
100
108
  });
101
109
  return signatureScheme;
102
110
  };
@@ -3377,28 +3385,34 @@ class DynamicWalletClient {
3377
3385
  }, this.getTraceContext(traceContext))));
3378
3386
  throw new Error('Timed out waiting for wallet creation ceremony to complete');
3379
3387
  }
3380
- async clientInitializeKeyGen({ chainName, thresholdSignatureScheme, bitcoinConfig }) {
3388
+ async clientInitializeKeyGen({ chainName, thresholdSignatureScheme, bitcoinConfig, variant }) {
3381
3389
  // Get the mpc signer
3382
3390
  const mpcSigner = getMPCSigner({
3383
3391
  chainName,
3384
3392
  baseRelayUrl: this.baseMPCRelayApiUrl,
3385
- bitcoinConfig
3393
+ bitcoinConfig,
3394
+ variant
3386
3395
  });
3387
3396
  const clientThreshold = core.getClientThreshold(thresholdSignatureScheme);
3388
3397
  const keygenInitResults = await Promise.all(Array(clientThreshold).fill(null).map(()=>mpcSigner.initKeygen()));
3389
3398
  return keygenInitResults;
3390
3399
  }
3391
- async derivePublicKey({ chainName, keyShare, derivationPath, bitcoinConfig }) {
3400
+ async derivePublicKey({ chainName, keyShare, derivationPath, bitcoinConfig, variant }) {
3392
3401
  const mpcSigner = getMPCSigner({
3393
3402
  chainName,
3394
3403
  baseRelayUrl: this.baseMPCRelayApiUrl,
3395
- bitcoinConfig
3404
+ bitcoinConfig,
3405
+ variant
3396
3406
  });
3397
3407
  let publicKey;
3398
3408
  if (mpcSigner instanceof web.Ecdsa) {
3399
3409
  publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
3400
3410
  } else if (mpcSigner instanceof web.ExportableEd25519) {
3401
3411
  publicKey = await mpcSigner.getPubkey(keyShare);
3412
+ } else if (mpcSigner instanceof web.Ed25519) {
3413
+ // Non-exportable Ed25519 (raw-scalar-imported wallets). Derivation is not
3414
+ // standardized for ed25519, so the root pubkey is taken with an undefined path.
3415
+ publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
3402
3416
  } else if (mpcSigner instanceof web.BIP340) {
3403
3417
  publicKey = await mpcSigner.deriveTweakPubkey(keyShare, derivationPath);
3404
3418
  }
@@ -3762,7 +3776,7 @@ class DynamicWalletClient {
3762
3776
  shouldRetry: (error)=>!isNonRetryableCeremonyError(error)
3763
3777
  });
3764
3778
  }
3765
- async runImportRawPrivateKeyAttempt({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, onError, onCeremonyComplete, traceContext, legacyWalletId, password, signedSessionId }) {
3779
+ async runImportRawPrivateKeyAttempt({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, onError, onCeremonyComplete, traceContext, legacyWalletId, password, signedSessionId, isRawScalarImport }) {
3766
3780
  const dynamicRequestId = uuid.v4();
3767
3781
  try {
3768
3782
  this.assertPasswordRequired(password);
@@ -3770,15 +3784,21 @@ class DynamicWalletClient {
3770
3784
  password,
3771
3785
  signedSessionId
3772
3786
  });
3787
+ // A raw-scalar import must run on the non-exportable `Ed25519` protocol for
3788
+ // the whole ceremony (init keygen, import, pubkey derivation) so the produced
3789
+ // key shares are consistent. Non-raw imports keep the default ('ed25519Exportable').
3790
+ const variant = isRawScalarImport ? 'ed25519Standard' : undefined;
3773
3791
  const mpcSigner = getMPCSigner({
3774
3792
  chainName,
3775
3793
  baseRelayUrl: this.baseMPCRelayApiUrl,
3776
- bitcoinConfig
3794
+ bitcoinConfig,
3795
+ variant
3777
3796
  });
3778
3797
  const clientKeygenInitResults = await this.clientInitializeKeyGen({
3779
3798
  chainName,
3780
3799
  thresholdSignatureScheme,
3781
- bitcoinConfig
3800
+ bitcoinConfig,
3801
+ variant
3782
3802
  });
3783
3803
  const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
3784
3804
  this.logger.info('[DynamicWaasWalletClient] Client key generation initialized', _extends({
@@ -3796,7 +3816,9 @@ class DynamicWalletClient {
3796
3816
  bitcoinConfig,
3797
3817
  onError,
3798
3818
  onCeremonyComplete,
3799
- legacyWalletId
3819
+ legacyWalletId,
3820
+ // Persist the protocol variant server-side so later sign/reshare read it back.
3821
+ variant
3800
3822
  });
3801
3823
  this.logger.info('[DynamicWaasWalletClient] Server key generation initialized', _extends({
3802
3824
  roomId,
@@ -3814,7 +3836,9 @@ class DynamicWalletClient {
3814
3836
  ...serverKeygenIds,
3815
3837
  ...otherClientKeygenIds
3816
3838
  ];
3817
- const importerKeygenResult = await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
3839
+ const importerKeygenResult = isRawScalarImport ? // uses the 32 bytes directly as the signing scalar instead of
3840
+ // SHA-512-expanding them as an RFC-8032 seed.
3841
+ await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds, true) : await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
3818
3842
  return importerKeygenResult;
3819
3843
  } else {
3820
3844
  const recipientKeygenResult = await mpcSigner.importPrivateKeyRecipient(roomId, threshold, currentInit, [
@@ -3831,7 +3855,8 @@ class DynamicWalletClient {
3831
3855
  chainName,
3832
3856
  keyShare: clientKeygenResult,
3833
3857
  derivationPath,
3834
- bitcoinConfig
3858
+ bitcoinConfig,
3859
+ variant
3835
3860
  });
3836
3861
  this.logger.info('[DynamicWaasWalletClient] Completed import of raw private key', _extends({
3837
3862
  rawPublicKey,
@@ -3866,6 +3891,8 @@ class DynamicWalletClient {
3866
3891
  if (typeof message !== 'string') {
3867
3892
  message = `0x${Buffer.from(message).toString('hex')}`;
3868
3893
  }
3894
+ // Note: the server signing party (wallet-service) selects the ed25519
3895
+ // protocol variant from its own persisted EAC, so it is NOT threaded here.
3869
3896
  const serializedContext = context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined;
3870
3897
  const useHttpTransport = this.featureFlags[core.FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT] === true && this.featureFlags[core.FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT_SIGNMESSAGE] === true;
3871
3898
  const params = {
@@ -3929,7 +3956,7 @@ class DynamicWalletClient {
3929
3956
  }
3930
3957
  return signatureBytes;
3931
3958
  }
3932
- async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted, traceContext, bitcoinConfig }) {
3959
+ async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted, traceContext, bitcoinConfig, variant }) {
3933
3960
  try {
3934
3961
  const chainConfig = core.getMPCChainConfig(chainName, bitcoinConfig);
3935
3962
  const signingAlgo = chainConfig.signingAlgorithm;
@@ -3942,7 +3969,7 @@ class DynamicWalletClient {
3942
3969
  });
3943
3970
  const tweak = this.convertTweakForBIP340(bitcoinConfig == null ? void 0 : bitcoinConfig.tweak);
3944
3971
  this.logger.info('Forward MPC enabled, signing message with forward MPC (new)', this.getTraceContext(traceContext));
3945
- const { signature: signatureBytes } = await this.apiClient.forwardMPCClient.signMessage({
3972
+ const { signature: signatureBytes } = await this.apiClient.forwardMPCClient.signMessage(_extends({
3946
3973
  keyshare: keyShare,
3947
3974
  message: messageToSign,
3948
3975
  relayDomain: this.baseMPCRelayApiUrl || '',
@@ -3954,7 +3981,9 @@ class DynamicWalletClient {
3954
3981
  traceContext,
3955
3982
  userId: this.userId,
3956
3983
  environmentId: this.environmentId
3957
- });
3984
+ }, variant === 'ed25519Standard' ? {
3985
+ ed25519Variant: 'standard'
3986
+ } : {}));
3958
3987
  if (!(signatureBytes instanceof Uint8Array)) {
3959
3988
  throw new TypeError(`Invalid signature format: expected Uint8Array, got ${typeof signatureBytes}`);
3960
3989
  }
@@ -3974,15 +4003,22 @@ class DynamicWalletClient {
3974
4003
  throw error;
3975
4004
  }
3976
4005
  }
3977
- async clientSign({ chainName, message, roomId: initialRoomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext, bitcoinConfig, refreshRoom }) {
4006
+ async clientSign({ chainName, message, roomId: initialRoomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext, bitcoinConfig, variant, refreshRoom }) {
3978
4007
  // Reassigned when forward MPC fails and we mint a fresh room for the
3979
4008
  // relay-based fallback below.
3980
4009
  let roomId = initialRoomId;
4010
+ // Non-exportable Ed25519 (raw-scalar import) derives its address at the
4011
+ // root — see derivePrivateKeyFromExport's identical guard. Unlike
4012
+ // ExportableEd25519 (which ignores this arg), Ed25519.sign() *applies* a
4013
+ // non-undefined path, so forwarding the wallet's stored derivation path
4014
+ // here would sign under a derived child key instead of the wallet's own.
4015
+ const effectiveDerivationPath = variant === 'ed25519Standard' ? undefined : derivationPath;
3981
4016
  try {
3982
4017
  const mpcSigner = getMPCSigner({
3983
4018
  chainName,
3984
4019
  baseRelayUrl: this.baseMPCRelayApiUrl,
3985
- bitcoinConfig
4020
+ bitcoinConfig,
4021
+ variant
3986
4022
  });
3987
4023
  const formattedMessage = isFormatted ? new web.MessageHash(message) : formatMessage(chainName, message);
3988
4024
  this.logger.debug('[DynamicWaasWalletClient] Starting client sign', _extends({
@@ -4006,12 +4042,13 @@ class DynamicWalletClient {
4006
4042
  message,
4007
4043
  roomId,
4008
4044
  keyShare,
4009
- derivationPath,
4045
+ derivationPath: effectiveDerivationPath,
4010
4046
  formattedMessage,
4011
4047
  dynamicRequestId,
4012
4048
  isFormatted,
4013
4049
  traceContext,
4014
- bitcoinConfig
4050
+ bitcoinConfig,
4051
+ variant
4015
4052
  });
4016
4053
  } catch (error) {
4017
4054
  const errorInfo = core.classifyForwardMpcError(error);
@@ -4055,7 +4092,7 @@ class DynamicWalletClient {
4055
4092
  if (!('sign' in mpcSigner)) {
4056
4093
  throw new TypeError(`Message signing is not supported for chain ${chainName} — use signTransaction instead`);
4057
4094
  }
4058
- const signature = await mpcSigner.sign(roomId, keyShare, messageToSign, derivationPath, tweak);
4095
+ const signature = await mpcSigner.sign(roomId, keyShare, messageToSign, effectiveDerivationPath, tweak);
4059
4096
  return signature;
4060
4097
  } catch (error) {
4061
4098
  logError({
@@ -4139,7 +4176,8 @@ class DynamicWalletClient {
4139
4176
  chainName,
4140
4177
  localShares,
4141
4178
  recordedKeygenIds,
4142
- bitcoinConfig
4179
+ bitcoinConfig,
4180
+ variant: this.ed25519VariantForWallet(accountAddress)
4143
4181
  });
4144
4182
  localKeygenIds = comparison.localKeygenIds;
4145
4183
  staleness = comparison.fresh ? 'fresh' : 'stale';
@@ -4205,11 +4243,14 @@ class DynamicWalletClient {
4205
4243
  * the server recorded for the wallet's Dynamic backups. Shared by the
4206
4244
  * stale-share heal (verifyAndRecoverStaleShare) and the backup preflight
4207
4245
  * (ensureLocalSharesAreFresh).
4208
- */ async compareShareGenerations({ accountAddress, chainName, localShares, recordedKeygenIds, bitcoinConfig }) {
4246
+ */ async compareShareGenerations({ accountAddress, chainName, localShares, recordedKeygenIds, bitcoinConfig, variant }) {
4209
4247
  const mpcSigner = getMPCSigner({
4210
4248
  chainName,
4211
4249
  baseRelayUrl: this.baseMPCRelayApiUrl,
4212
- bitcoinConfig: bitcoinConfig != null ? bitcoinConfig : this.getBitcoinConfigForChain(chainName, accountAddress)
4250
+ bitcoinConfig: bitcoinConfig != null ? bitcoinConfig : this.getBitcoinConfigForChain(chainName, accountAddress),
4251
+ // Raw-scalar (non-exportable Ed25519) wallets: build the matching signer so
4252
+ // getKeygenIdForShare deserializes the Ed25519 share (else "Invalid discriminant").
4253
+ variant
4213
4254
  });
4214
4255
  const localKeygenIds = await Promise.all(localShares.map((share)=>this.getKeygenIdForShare(mpcSigner, share)));
4215
4256
  const recordedSet = new Set(recordedKeygenIds);
@@ -4367,6 +4408,7 @@ class DynamicWalletClient {
4367
4408
  dynamicRequestId,
4368
4409
  traceContext,
4369
4410
  bitcoinConfig,
4411
+ variant: wallet.variant,
4370
4412
  // On forward-MPC fallback, re-run the server sign with no roomId so a
4371
4413
  // brand-new server-joined room is created for the relay ceremony.
4372
4414
  refreshRoom: async ()=>{
@@ -4666,7 +4708,11 @@ class DynamicWalletClient {
4666
4708
  const mpcSigner = getMPCSigner({
4667
4709
  chainName,
4668
4710
  baseRelayUrl: this.baseMPCRelayApiUrl,
4669
- bitcoinConfig
4711
+ bitcoinConfig,
4712
+ // Raw-scalar (non-exportable Ed25519) wallets must refresh on the same
4713
+ // protocol their shares were created with, or the ceremony fails to
4714
+ // deserialize them ("Invalid discriminant").
4715
+ variant: this.ed25519VariantForWallet(accountAddress)
4670
4716
  });
4671
4717
  // Ensure client key shares exist before hitting the API
4672
4718
  const clientKeyShares = await this.ensureClientShare(accountAddress);
@@ -4848,11 +4894,26 @@ class DynamicWalletClient {
4848
4894
  }
4849
4895
  return mpcSigner.exportID(share);
4850
4896
  }
4851
- async getExportId({ chainName, clientKeyShare, bitcoinConfig }) {
4897
+ /**
4898
+ * The ed25519 protocol variant for an existing wallet, read from the wallet map
4899
+ * (sourced from `walletProperties.settings.variant`, persisted at import).
4900
+ *
4901
+ * It is per-wallet protocol metadata — like `thresholdSignatureScheme` or
4902
+ * `addressType` — and the single source of truth for which signer to build when
4903
+ * operating on a wallet's key shares. Wallets imported from a raw ed25519 scalar
4904
+ * (e.g. Fireblocks Embedded Wallets) are `'ed25519Standard'` (non-exportable `Ed25519`);
4905
+ * everything else is `undefined` → the default exportable protocol. Using the
4906
+ * wrong one fails to deserialize the key share with "Invalid discriminant".
4907
+ */ ed25519VariantForWallet(accountAddress) {
4908
+ var _this_getWalletFromMap;
4909
+ return (_this_getWalletFromMap = this.getWalletFromMap(accountAddress)) == null ? void 0 : _this_getWalletFromMap.variant;
4910
+ }
4911
+ async getExportId({ chainName, clientKeyShare, bitcoinConfig, accountAddress }) {
4852
4912
  const mpcSigner = getMPCSigner({
4853
4913
  chainName,
4854
4914
  baseRelayUrl: this.baseMPCRelayApiUrl,
4855
- bitcoinConfig
4915
+ bitcoinConfig,
4916
+ variant: accountAddress ? this.ed25519VariantForWallet(accountAddress) : undefined
4856
4917
  });
4857
4918
  try {
4858
4919
  const exportId = await this.getKeygenIdForShare(mpcSigner, clientKeyShare);
@@ -4900,7 +4961,8 @@ class DynamicWalletClient {
4900
4961
  return this.getExportId({
4901
4962
  chainName,
4902
4963
  clientKeyShare,
4903
- bitcoinConfig
4964
+ bitcoinConfig,
4965
+ accountAddress
4904
4966
  });
4905
4967
  }
4906
4968
  /**
@@ -4941,7 +5003,8 @@ class DynamicWalletClient {
4941
5003
  const existingClientKeygenIds = await Promise.all(existingClientKeyShares.map(async (keyShare)=>await this.getExportId({
4942
5004
  chainName,
4943
5005
  clientKeyShare: keyShare,
4944
- bitcoinConfig
5006
+ bitcoinConfig,
5007
+ accountAddress
4945
5008
  })));
4946
5009
  return {
4947
5010
  newClientInitKeygenResults,
@@ -5010,7 +5073,8 @@ class DynamicWalletClient {
5010
5073
  const existingClientKeygenId = await this.getExportId({
5011
5074
  chainName,
5012
5075
  clientKeyShare: existingClientShare,
5013
- bitcoinConfig
5076
+ bitcoinConfig,
5077
+ accountAddress
5014
5078
  });
5015
5079
  // Gate matches internalRefresh/Reshare — SSE error rejects awaitTerminal.
5016
5080
  let newShareSetId;
@@ -5619,7 +5683,11 @@ class DynamicWalletClient {
5619
5683
  const mpcSigner = getMPCSigner({
5620
5684
  chainName,
5621
5685
  baseRelayUrl: this.baseMPCRelayApiUrl,
5622
- bitcoinConfig
5686
+ bitcoinConfig,
5687
+ // Raw-scalar (non-exportable Ed25519) wallets must reshare on the same
5688
+ // protocol their shares were created with, or the ceremony fails to
5689
+ // deserialize them ("Invalid discriminant").
5690
+ variant: this.ed25519VariantForWallet(accountAddress)
5623
5691
  });
5624
5692
  let existingReshareResults;
5625
5693
  let newReshareResults;
@@ -5983,6 +6051,9 @@ class DynamicWalletClient {
5983
6051
  if (mpcSigner instanceof web.ExportableEd25519) {
5984
6052
  return new web.ExportableEd25519KeygenResult(extractedPubkey, secretShare);
5985
6053
  }
6054
+ if (mpcSigner instanceof web.Ed25519) {
6055
+ return new web.Ed25519KeygenResult(extractedPubkey, secretShare);
6056
+ }
5986
6057
  return new web.BIP340KeygenResult(extractedPubkey, secretShare);
5987
6058
  }
5988
6059
  async exportKey({ accountAddress, chainName, bitcoinConfig, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, traceContext }) {
@@ -6013,13 +6084,15 @@ class DynamicWalletClient {
6013
6084
  const mpcSigner = getMPCSigner({
6014
6085
  chainName,
6015
6086
  baseRelayUrl: this.baseMPCRelayApiUrl,
6016
- bitcoinConfig
6087
+ bitcoinConfig,
6088
+ variant: wallet.variant
6017
6089
  });
6018
6090
  const reconstructedKeyShare = await this.getReconstructedKeyShare(accountAddress, mpcSigner);
6019
6091
  const exportId = await this.getExportId({
6020
6092
  chainName,
6021
6093
  clientKeyShare: reconstructedKeyShare,
6022
- bitcoinConfig
6094
+ bitcoinConfig,
6095
+ accountAddress: wallet.accountAddress
6023
6096
  });
6024
6097
  const data = await this.apiClient.exportKey({
6025
6098
  walletId: wallet.walletId,
@@ -6162,20 +6235,35 @@ class DynamicWalletClient {
6162
6235
  if (mpcSigner instanceof web.Ecdsa) {
6163
6236
  return mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
6164
6237
  } else if (mpcSigner instanceof web.ExportableEd25519) {
6238
+ // ExportableEd25519's exportFullPrivateKey already returns the RFC-8032
6239
+ // form (hex of seed||pubkey); pass it straight through.
6165
6240
  return keyExportRaw;
6241
+ } else if (mpcSigner instanceof web.Ed25519) {
6242
+ // Non-exportable Ed25519's exportFullPrivateKey returns an `spriv`
6243
+ // (extended private key), NOT a raw hex key — it must be parsed to the
6244
+ // actual private scalar. Returning the spriv verbatim makes the chain
6245
+ // client hex-decode it into an empty buffer (blank export field).
6246
+ //
6247
+ // This protocol is only used for raw-scalar import, whose public key is
6248
+ // the ROOT scalar (`A = s·B`, derived at import via derivePubkey(_, undefined)).
6249
+ // The chain-config derivation path stored on the wallet (e.g. Solana's
6250
+ // [44,501,0,0,0]) must NOT be applied here — deriving a child scalar would
6251
+ // export a key that doesn't correspond to the wallet's address.
6252
+ return mpcSigner.derivePrivateKeyFromSpriv(keyExportRaw);
6166
6253
  } else if (mpcSigner instanceof web.BIP340) {
6167
6254
  return mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
6168
6255
  }
6169
6256
  return undefined;
6170
6257
  }
6171
- async offlineExportKey({ chainName, keyShares, derivationPath }) {
6258
+ async offlineExportKey({ chainName, keyShares, derivationPath, variant }) {
6172
6259
  try {
6173
6260
  if (!keyShares || keyShares.length < 2) {
6174
6261
  throw new Error(`Must provide at least min threshold of key shares`);
6175
6262
  }
6176
6263
  const mpcSigner = getMPCSigner({
6177
6264
  chainName,
6178
- baseRelayUrl: this.baseMPCRelayApiUrl
6265
+ baseRelayUrl: this.baseMPCRelayApiUrl,
6266
+ variant
6179
6267
  });
6180
6268
  const walletKeyShares = keyShares.map((keyShare)=>{
6181
6269
  if (!('pubkey' in keyShare)) {
@@ -6194,6 +6282,10 @@ class DynamicWalletClient {
6194
6282
  derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
6195
6283
  } else if (mpcSigner instanceof web.ExportableEd25519) {
6196
6284
  derivedPrivateKey = keyExportRaw;
6285
+ } else if (mpcSigner instanceof web.Ed25519) {
6286
+ // Non-exportable Ed25519 (raw-scalar) returns an `spriv`, not raw hex —
6287
+ // parse it to the root scalar (see derivePrivateKeyFromExport).
6288
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromSpriv(keyExportRaw);
6197
6289
  } else if (mpcSigner instanceof web.BIP340) {
6198
6290
  derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
6199
6291
  }
@@ -6399,7 +6491,7 @@ class DynamicWalletClient {
6399
6491
  * @param thresholdSignatureScheme - The TSS scheme used
6400
6492
  * @param derivationPath - Optional derivation path (will be computed from chainConfig if not provided)
6401
6493
  * @param additionalProps - Any chain-specific additional properties to merge
6402
- */ initializeWalletMapEntry({ accountAddress, walletId, chainName, thresholdSignatureScheme, derivationPath, shareSetId, shareSetType, otherShareSets, additionalProps = {} }) {
6494
+ */ initializeWalletMapEntry({ accountAddress, walletId, chainName, thresholdSignatureScheme, derivationPath, shareSetId, shareSetType, otherShareSets, variant, additionalProps = {} }) {
6403
6495
  this.updateWalletMap(accountAddress, _extends({
6404
6496
  accountAddress,
6405
6497
  walletId,
@@ -6413,6 +6505,8 @@ class DynamicWalletClient {
6413
6505
  shareSetType
6414
6506
  } : {}, otherShareSets ? {
6415
6507
  otherShareSets
6508
+ } : {}, variant ? {
6509
+ variant
6416
6510
  } : {}, {
6417
6511
  clientKeySharesBackupInfo: getClientKeyShareBackupInfo()
6418
6512
  }, additionalProps));
@@ -6632,7 +6726,7 @@ class DynamicWalletClient {
6632
6726
  tokenSource: googleDriveTokenSource
6633
6727
  } : {});
6634
6728
  }
6635
- async publishDelegatedShare({ walletId, shareSetId, delegatedShare, signedSessionId, dynamicRequestId, chainName, bitcoinConfig, isPasswordEncrypted }) {
6729
+ async publishDelegatedShare({ walletId, shareSetId, delegatedShare, signedSessionId, dynamicRequestId, chainName, accountAddress, bitcoinConfig, isPasswordEncrypted }) {
6636
6730
  var _publicKey_key, _publicKey_key1, _publicKey_key2;
6637
6731
  const publicKey = await this.apiClient.getDelegatedEncryptionKey({
6638
6732
  environmentId: this.environmentId
@@ -6657,7 +6751,8 @@ class DynamicWalletClient {
6657
6751
  chainName,
6658
6752
  clientKeyShare: delegatedShare,
6659
6753
  bitcoinConfig,
6660
- walletId
6754
+ walletId,
6755
+ accountAddress
6661
6756
  });
6662
6757
  return {
6663
6758
  location: core.BackupLocation.DELEGATED,
@@ -6823,6 +6918,7 @@ class DynamicWalletClient {
6823
6918
  signedSessionId: resolvedSignedSessionId,
6824
6919
  dynamicRequestId,
6825
6920
  chainName: walletData.chainName,
6921
+ accountAddress,
6826
6922
  bitcoinConfig,
6827
6923
  isPasswordEncrypted
6828
6924
  }), {
@@ -6991,7 +7087,8 @@ class DynamicWalletClient {
6991
7087
  accountAddress,
6992
7088
  chainName,
6993
7089
  localShares,
6994
- recordedKeygenIds
7090
+ recordedKeygenIds,
7091
+ variant: walletData.variant
6995
7092
  });
6996
7093
  if (fresh) return;
6997
7094
  this.logger.warn('[storeEncryptedBackupByWallet] Stale local shares detected; refreshing local storage', {
@@ -8416,9 +8513,17 @@ class DynamicWalletClient {
8416
8513
  const user = await this.getUserWithEnvCheck(dynamicRequestId);
8417
8514
  const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
8418
8515
  for (const vc of waasWallets != null ? waasWallets : []){
8516
+ var _props_settings, _this_getWalletFromMap;
8419
8517
  const addr = vc.address;
8420
8518
  const props = vc.walletProperties;
8421
- this.updateWalletMap(addr, {
8519
+ var _props_settings_variant, _ref;
8520
+ // Resolve the ed25519 protocol variant: server-surfaced (walletProperties
8521
+ // .settings or top-level) first, then any locally-seeded value (set at raw
8522
+ // import). Required on a fresh session (no local seed) so recover/sign
8523
+ // rebuild the non-exportable Ed25519 signer for raw-scalar wallets instead
8524
+ // of defaulting to exportable → "Invalid discriminant".
8525
+ 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;
8526
+ this.updateWalletMap(addr, _extends({
8422
8527
  walletId: vc.id,
8423
8528
  chainName: core.verifiedCredentialNameToChainEnum[vc.chain],
8424
8529
  accountAddress: addr,
@@ -8431,7 +8536,9 @@ class DynamicWalletClient {
8431
8536
  shareSetId: props == null ? void 0 : props.shareSetId,
8432
8537
  shareSetType: props == null ? void 0 : props.shareSetType,
8433
8538
  otherShareSets: props == null ? void 0 : props.otherShareSets
8434
- });
8539
+ }, resolvedVariant ? {
8540
+ variant: resolvedVariant
8541
+ } : {}));
8435
8542
  }
8436
8543
  if (walletOperation !== core.WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
8437
8544
  accountAddress,
@@ -8809,8 +8916,8 @@ class DynamicWalletClient {
8809
8916
  this.userId = user.id;
8810
8917
  const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
8811
8918
  const wallets = waasWallets.map((vc)=>{
8812
- var _this_getWalletFromMap, _vc_walletProperties, _vc_walletProperties1, _vc_walletProperties2, _vc_walletProperties3, _vc_walletProperties4, _vc_walletProperties5, _vc_walletProperties6;
8813
- var _this_getWalletFromMap_derivationPath;
8919
+ 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;
8920
+ var _this_getWalletFromMap_derivationPath, _vc_walletProperties_settings_variant, _ref;
8814
8921
  return {
8815
8922
  walletId: vc.id,
8816
8923
  chainName: core.verifiedCredentialNameToChainEnum[vc.chain],
@@ -8828,14 +8935,18 @@ class DynamicWalletClient {
8828
8935
  // On-sign-on settings (shouldRefreshOnNextSignOn / reshareOnNextSignOn /
8829
8936
  // revokeOnNextSignOn). Surfaced so the SDK can self-drive the on-sign-on
8830
8937
  // orchestration instead of the host reading these flags.
8831
- settings: (_vc_walletProperties6 = vc.walletProperties) == null ? void 0 : _vc_walletProperties6.settings
8938
+ settings: (_vc_walletProperties6 = vc.walletProperties) == null ? void 0 : _vc_walletProperties6.settings,
8939
+ // Prefer the server-persisted variant (surfaced via walletProperties.settings),
8940
+ // then the locally-known value (set at raw import) so same-session
8941
+ // import → sign works before the server has surfaced it.
8942
+ 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
8832
8943
  };
8833
8944
  });
8834
8945
  const existingWalletMap = this.walletMap;
8835
8946
  this.walletMap = wallets.reduce((acc, wallet)=>{
8836
8947
  const normalizedAddress = normalizeAddress(wallet.accountAddress);
8837
8948
  const existingWallet = existingWalletMap[normalizedAddress];
8838
- acc[normalizedAddress] = {
8949
+ acc[normalizedAddress] = _extends({
8839
8950
  walletId: wallet.walletId,
8840
8951
  chainName: wallet.chainName,
8841
8952
  accountAddress: wallet.accountAddress,
@@ -8848,7 +8959,9 @@ class DynamicWalletClient {
8848
8959
  shareSetType: wallet.shareSetType,
8849
8960
  businessAccountId: wallet.businessAccountId,
8850
8961
  otherShareSets: wallet.otherShareSets
8851
- };
8962
+ }, wallet.variant ? {
8963
+ variant: wallet.variant
8964
+ } : {});
8852
8965
  return acc;
8853
8966
  }, {});
8854
8967
  this.logger.info('[walletMap] getWallets: walletMap updated from API', {
@@ -9302,6 +9415,46 @@ DynamicWalletClient.roomsPersistDirty = false;
9302
9415
  // rooms back into the new user's storage (TOCTOU on the create→merge gap).
9303
9416
  DynamicWalletClient.roomsGeneration = 0;
9304
9417
 
9418
+ /**
9419
+ * Derive the 32-byte ed25519 public key from a **raw** 32-byte signing scalar
9420
+ * (`A = s·B`).
9421
+ *
9422
+ * This is NOT the same as deriving a public key from an RFC-8032 seed: the
9423
+ * standard ed25519 key-generation step takes a 32-byte seed and SHA-512-expands
9424
+ * it (then clamps) to obtain the scalar. A scalar exported from an external MPC
9425
+ * system (e.g. Fireblocks Embedded Wallets) has no preimage seed, so it must be
9426
+ * used directly as the scalar. Using `Keypair.fromSecretKey` / `keyPairFromSeed`
9427
+ * on such bytes would re-expand them and produce the wrong public key/address.
9428
+ *
9429
+ * @param scalar Raw 32-byte ed25519 scalar, little-endian, as hex (with or
9430
+ * without a `0x` prefix) or as raw bytes.
9431
+ * @returns The compressed 32-byte ed25519 public key.
9432
+ */ const getEd25519PublicKeyFromRawScalar = (scalar)=>{
9433
+ let bytes;
9434
+ if (typeof scalar === 'string') {
9435
+ const hex = scalar.replace(/^0x/, '');
9436
+ // Validate before hexToBytes so malformed input fails with a clear, own
9437
+ // error rather than an opaque/implementation-specific one from the decoder.
9438
+ if (!/^[0-9a-fA-F]+$/.test(hex)) {
9439
+ throw new Error('Invalid raw ed25519 scalar: expected a hex string');
9440
+ }
9441
+ bytes = utils.hexToBytes(hex);
9442
+ } else {
9443
+ bytes = scalar;
9444
+ }
9445
+ if (bytes.length !== 32) {
9446
+ throw new Error(`Invalid raw ed25519 scalar length: ${bytes.length}, expected 32`);
9447
+ }
9448
+ // Interpret the scalar little-endian and reduce mod the curve order. We do NOT
9449
+ // clamp here: the caller provides the actual signing scalar `s`, and the public
9450
+ // key is exactly `s·B`.
9451
+ const reduced = utils.bytesToNumberLE(bytes) % ed25519.ed25519.CURVE.n;
9452
+ if (reduced === 0n) {
9453
+ throw new Error('Invalid raw ed25519 scalar: reduces to zero mod the curve order');
9454
+ }
9455
+ return ed25519.ed25519.ExtendedPoint.BASE.multiply(reduced).toRawBytes();
9456
+ };
9457
+
9305
9458
  Object.defineProperty(exports, "Logger", {
9306
9459
  enumerable: true,
9307
9460
  get: function () { return core.Logger; }
@@ -9416,6 +9569,7 @@ exports.getBitcoinAddressTypeFromDerivationPath = getBitcoinAddressTypeFromDeriv
9416
9569
  exports.getClientKeyShareBackupInfo = getClientKeyShareBackupInfo;
9417
9570
  exports.getClientKeyShareExportFileName = getClientKeyShareExportFileName;
9418
9571
  exports.getDelegatedShareSet = getDelegatedShareSet;
9572
+ exports.getEd25519PublicKeyFromRawScalar = getEd25519PublicKeyFromRawScalar;
9419
9573
  exports.getGoogleOAuthAccountId = getGoogleOAuthAccountId;
9420
9574
  exports.getHttpStatus = getHttpStatus;
9421
9575
  exports.getICloudBackup = getICloudBackup;