@fhevm/sdk 1.1.0-alpha.5 → 1.1.0-alpha.6

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.
Files changed (145) hide show
  1. package/_cjs/core/_version.js +1 -1
  2. package/_cjs/core/base/bytes.js +2 -1
  3. package/_cjs/core/base/bytes.js.map +1 -1
  4. package/_cjs/core/base/environment.js +122 -0
  5. package/_cjs/core/base/environment.js.map +1 -0
  6. package/_cjs/core/base/errors/Sha256VerificationError.js +15 -0
  7. package/_cjs/core/base/errors/Sha256VerificationError.js.map +1 -0
  8. package/_cjs/core/base/inflate.js +282 -0
  9. package/_cjs/core/base/inflate.js.map +1 -0
  10. package/_cjs/core/base/isomorphicFs.js +24 -0
  11. package/_cjs/core/base/isomorphicFs.js.map +1 -0
  12. package/_cjs/core/base/isomorphicWorker.js +42 -111
  13. package/_cjs/core/base/isomorphicWorker.js.map +1 -1
  14. package/_cjs/core/base/wasm.js +15 -55
  15. package/_cjs/core/base/wasm.js.map +1 -1
  16. package/_cjs/core/kms/TransportKeyPair-p.js +0 -4
  17. package/_cjs/core/kms/TransportKeyPair-p.js.map +1 -1
  18. package/_cjs/core/kms/createKmsUserDecryptEip712.js +5 -1
  19. package/_cjs/core/kms/createKmsUserDecryptEip712.js.map +1 -1
  20. package/_cjs/core/modules/decrypt/module/init-p.js +124 -39
  21. package/_cjs/core/modules/decrypt/module/init-p.js.map +1 -1
  22. package/_cjs/core/modules/encrypt/module/init-p.js +148 -45
  23. package/_cjs/core/modules/encrypt/module/init-p.js.map +1 -1
  24. package/_cjs/core/runtime/HyperWasmSolver-p.js +4 -4
  25. package/_cjs/core/runtime/initFhevmEncryptRuntime-p.js +1 -1
  26. package/_cjs/core/runtime/initFhevmRuntime-p.js +1 -1
  27. package/_cjs/wasm/tfhe/TfheApi.d.ts +12 -12
  28. package/_cjs/wasm/tfhe/loadTfheLib.js +11 -11
  29. package/_cjs/wasm/tfhe/loadTfheLib.js.map +2 -2
  30. package/_cjs/wasm/tfhe/v1.5.3/startWorkers.js +26 -9
  31. package/_cjs/wasm/tfhe/v1.5.3/startWorkers.js.map +2 -2
  32. package/_cjs/wasm/tfhe/v1.5.3/tfhe.d.ts +3 -0
  33. package/_cjs/wasm/tfhe/{v1.6.1 → v1.6.2}/startWorkers.js +28 -11
  34. package/_cjs/wasm/tfhe/v1.6.2/startWorkers.js.map +7 -0
  35. package/{_esm/wasm/tfhe/v1.6.1 → _cjs/wasm/tfhe/v1.6.2}/tfhe-worker.mjs +40 -40
  36. package/{_esm/wasm/tfhe/v1.6.1 → _cjs/wasm/tfhe/v1.6.2}/tfhe.d.ts +88 -85
  37. package/_cjs/wasm/tfhe/{v1.6.1 → v1.6.2}/tfhe.js +245 -442
  38. package/_cjs/wasm/tfhe/v1.6.2/tfhe.js.map +7 -0
  39. package/_cjs/wasm/tfhe/v1.6.2/tfhe_bg.wasm +0 -0
  40. package/_cjs/wasm/tfhe/v1.6.2/tfhe_bg.wasm.base64.js +35 -0
  41. package/_cjs/wasm/tfhe/v1.6.2/tfhe_bg.wasm.base64.js.map +7 -0
  42. package/_cjs/wasm/tkms/loadKmsLib.js.map +1 -1
  43. package/_cjs/wasm/wasmBaseUrl.js +8 -19
  44. package/_cjs/wasm/wasmBaseUrl.js.map +2 -2
  45. package/_esm/core/_version.js +1 -1
  46. package/_esm/core/base/bytes.js +2 -1
  47. package/_esm/core/base/bytes.js.map +1 -1
  48. package/_esm/core/base/environment.js +164 -0
  49. package/_esm/core/base/environment.js.map +1 -0
  50. package/_esm/core/base/errors/Sha256VerificationError.js +11 -0
  51. package/_esm/core/base/errors/Sha256VerificationError.js.map +1 -0
  52. package/_esm/core/base/inflate.js +337 -0
  53. package/_esm/core/base/inflate.js.map +1 -0
  54. package/_esm/core/base/isomorphicFs.js +21 -0
  55. package/_esm/core/base/isomorphicFs.js.map +1 -0
  56. package/_esm/core/base/isomorphicWorker.js +92 -85
  57. package/_esm/core/base/isomorphicWorker.js.map +1 -1
  58. package/_esm/core/base/wasm.js +25 -31
  59. package/_esm/core/base/wasm.js.map +1 -1
  60. package/_esm/core/kms/TransportKeyPair-p.js +0 -4
  61. package/_esm/core/kms/TransportKeyPair-p.js.map +1 -1
  62. package/_esm/core/kms/createKmsUserDecryptEip712.js +5 -1
  63. package/_esm/core/kms/createKmsUserDecryptEip712.js.map +1 -1
  64. package/_esm/core/modules/decrypt/module/init-p.js +109 -54
  65. package/_esm/core/modules/decrypt/module/init-p.js.map +1 -1
  66. package/_esm/core/modules/encrypt/module/init-p.js +300 -53
  67. package/_esm/core/modules/encrypt/module/init-p.js.map +1 -1
  68. package/_esm/core/runtime/HyperWasmSolver-p.js +4 -4
  69. package/_esm/core/runtime/initFhevmEncryptRuntime-p.js +1 -1
  70. package/_esm/core/runtime/initFhevmRuntime-p.js +1 -1
  71. package/_esm/wasm/tfhe/TfheApi.d.ts +12 -12
  72. package/_esm/wasm/tfhe/loadTfheLib.js +11 -11
  73. package/_esm/wasm/tfhe/v1.5.3/startWorkers.js +41 -20
  74. package/_esm/wasm/tfhe/v1.5.3/tfhe.d.ts +3 -0
  75. package/_esm/wasm/tfhe/{v1.6.1 → v1.6.2}/startWorkers.js +46 -25
  76. package/{_cjs/wasm/tfhe/v1.6.1 → _esm/wasm/tfhe/v1.6.2}/tfhe-worker.mjs +40 -40
  77. package/{_types/wasm/tfhe/v1.6.1 → _esm/wasm/tfhe/v1.6.2}/tfhe.d.ts +88 -85
  78. package/_esm/wasm/tfhe/{v1.6.1 → v1.6.2}/tfhe.js +245 -442
  79. package/_esm/wasm/tfhe/v1.6.2/tfhe_bg.wasm +0 -0
  80. package/_esm/wasm/tfhe/v1.6.2/tfhe_bg.wasm.base64.js +7 -0
  81. package/_esm/wasm/wasmBaseUrl.js +17 -54
  82. package/_types/core/base/bytes.d.ts.map +1 -1
  83. package/_types/core/base/environment.d.ts +92 -0
  84. package/_types/core/base/environment.d.ts.map +1 -0
  85. package/_types/core/base/errors/Sha256VerificationError.d.ts +14 -0
  86. package/_types/core/base/errors/Sha256VerificationError.d.ts.map +1 -0
  87. package/_types/core/base/inflate.d.ts +25 -0
  88. package/_types/core/base/inflate.d.ts.map +1 -0
  89. package/_types/core/base/isomorphicFs.d.ts +2 -0
  90. package/_types/core/base/isomorphicFs.d.ts.map +1 -0
  91. package/_types/core/base/isomorphicWorker.d.ts +27 -43
  92. package/_types/core/base/isomorphicWorker.d.ts.map +1 -1
  93. package/_types/core/base/wasm.d.ts +0 -8
  94. package/_types/core/base/wasm.d.ts.map +1 -1
  95. package/_types/core/kms/TransportKeyPair-p.d.ts.map +1 -1
  96. package/_types/core/kms/createKmsUserDecryptEip712.d.ts.map +1 -1
  97. package/_types/core/modules/decrypt/module/init-p.d.ts.map +1 -1
  98. package/_types/core/modules/encrypt/module/init-p.d.ts.map +1 -1
  99. package/_types/core/types/wasmAssets.d.ts.map +1 -1
  100. package/_types/wasm/tfhe/TfheApi.d.ts +12 -12
  101. package/_types/wasm/tfhe/v1.5.3/tfhe.d.ts +3 -0
  102. package/{wasm/tfhe/v1.6.1 → _types/wasm/tfhe/v1.6.2}/tfhe.d.ts +88 -85
  103. package/core/_version.ts +1 -1
  104. package/core/base/bytes.ts +2 -1
  105. package/core/base/environment.ts +210 -0
  106. package/core/base/errors/Sha256VerificationError.ts +22 -0
  107. package/core/base/inflate.ts +373 -0
  108. package/core/base/isomorphicFs.ts +23 -0
  109. package/core/base/isomorphicWorker.ts +96 -123
  110. package/core/base/wasm.ts +28 -35
  111. package/core/kms/TransportKeyPair-p.ts +0 -5
  112. package/core/kms/createKmsUserDecryptEip712.ts +8 -1
  113. package/core/modules/decrypt/module/init-p.ts +148 -59
  114. package/core/modules/encrypt/module/init-p.ts +368 -63
  115. package/core/runtime/HyperWasmSolver-p.ts +4 -4
  116. package/core/runtime/initFhevmEncryptRuntime-p.ts +1 -1
  117. package/core/runtime/initFhevmRuntime-p.ts +1 -1
  118. package/core/types/wasmAssets.ts +58 -0
  119. package/package.json +1 -1
  120. package/wasm/tfhe/TfheApi.d.ts +12 -12
  121. package/wasm/tfhe/loadTfheLib.js +11 -11
  122. package/wasm/tfhe/v1.5.3/startWorkers.js +41 -20
  123. package/wasm/tfhe/v1.5.3/tfhe.d.ts +3 -0
  124. package/wasm/tfhe/v1.6.0-dev/startWorkers.js +41 -20
  125. package/wasm/tfhe/v1.6.0-dev/tfhe.d.ts +3 -0
  126. package/wasm/tfhe/{v1.6.1 → v1.6.2}/startWorkers.js +46 -25
  127. package/wasm/tfhe/{v1.6.1 → v1.6.2}/tfhe-worker.mjs +40 -40
  128. package/{_cjs/wasm/tfhe/v1.6.1 → wasm/tfhe/v1.6.2}/tfhe.d.ts +88 -85
  129. package/wasm/tfhe/{v1.6.1 → v1.6.2}/tfhe.js +245 -442
  130. package/wasm/tfhe/v1.6.2/tfhe_bg.wasm +0 -0
  131. package/wasm/tfhe/v1.6.2/tfhe_bg.wasm.base64.js +7 -0
  132. package/wasm/wasmBaseUrl.js +17 -54
  133. package/_cjs/wasm/tfhe/v1.6.1/startWorkers.js.map +0 -7
  134. package/_cjs/wasm/tfhe/v1.6.1/tfhe.js.map +0 -7
  135. package/_cjs/wasm/tfhe/v1.6.1/tfhe_bg.wasm +0 -0
  136. package/_cjs/wasm/tfhe/v1.6.1/tfhe_bg.wasm.base64.js +0 -35
  137. package/_cjs/wasm/tfhe/v1.6.1/tfhe_bg.wasm.base64.js.map +0 -7
  138. package/_esm/wasm/tfhe/v1.6.1/tfhe_bg.wasm +0 -0
  139. package/_esm/wasm/tfhe/v1.6.1/tfhe_bg.wasm.base64.js +0 -7
  140. package/wasm/tfhe/v1.6.1/tfhe_bg.wasm +0 -0
  141. package/wasm/tfhe/v1.6.1/tfhe_bg.wasm.base64.js +0 -7
  142. /package/_cjs/wasm/tfhe/{v1.6.1 → v1.6.2}/tfhe_bg.wasm.base64.d.ts +0 -0
  143. /package/_esm/wasm/tfhe/{v1.6.1 → v1.6.2}/tfhe_bg.wasm.base64.d.ts +0 -0
  144. /package/_types/wasm/tfhe/{v1.6.1 → v1.6.2}/tfhe_bg.wasm.base64.d.ts +0 -0
  145. /package/wasm/tfhe/{v1.6.1 → v1.6.2}/tfhe_bg.wasm.base64.d.ts +0 -0
package/core/base/wasm.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { verifySha256 } from './bytes.js';
2
2
  import { FetchError } from './errors/FetchError.js';
3
3
  import { isDataUrlFetchSupported } from './fetch.js';
4
- import { isBrowserLike } from './isomorphicWorker.js';
4
+ import { getNodeBuffer, getNodeFs, getNodeUrl, isBrowserLike, supportsDecompressionStream } from './environment.js';
5
+ import { inflateDecompress } from './inflate.js';
5
6
 
6
7
  /**
7
8
  * Decodes a base64 string to bytes using the fastest available method.
@@ -12,8 +13,12 @@ import { isBrowserLike } from './isomorphicWorker.js';
12
13
  */
13
14
  async function _isomorphicDecodeBase64(base64: string): Promise<BufferSource> {
14
15
  if (!isBrowserLike()) {
15
- const { Buffer } = await import('node:buffer');
16
- return Buffer.from(base64, 'base64');
16
+ const NodeBuffer = await getNodeBuffer();
17
+ if (NodeBuffer) {
18
+ return NodeBuffer.from(base64, 'base64');
19
+ }
20
+ // Not a browser, but no node:buffer either (edge runtime). Fall through to
21
+ // the web-standard data-URL fetch / atob paths below, which edge supports.
17
22
  }
18
23
 
19
24
  if (await isDataUrlFetchSupported()) {
@@ -47,43 +52,21 @@ async function _isomorphicFetchWasmResponse(wasmUrl: URL): Promise<Response> {
47
52
 
48
53
  async function _isomorphicReadWasmBytes(wasmUrl: URL): Promise<BufferSource> {
49
54
  if (!isBrowserLike() && wasmUrl.protocol === 'file:') {
50
- const { readFile } = await import('node:fs/promises');
51
- const { fileURLToPath } = await import('node:url');
52
-
53
- // Node's Buffer extends Uint8Array but TS 5.7+ considers its .buffer as
54
- // ArrayBufferLike (includes SharedArrayBuffer), making it incompatible with
55
- // BufferSource. At runtime Buffer is always backed by ArrayBuffer, so the
56
- // cast at WebAssembly.compile is safe and avoids copying.
57
- return readFile(fileURLToPath(wasmUrl));
55
+ const [fs, url] = await Promise.all([getNodeFs(), getNodeUrl()]);
56
+ if (fs && url) {
57
+ // The BufferSource-safe cast for Node's Buffer lives in getNodeFs's
58
+ // return type (see environment.ts), so the bytes can flow straight into
59
+ // WebAssembly.compile without copying.
60
+ return fs.readFile(url.fileURLToPath(wasmUrl));
61
+ }
62
+ // Not a browser, but no node:fs either (edge runtime). Fall through to
63
+ // fetch below — a file: URL will fail there, surfacing a clear error.
58
64
  }
59
65
 
60
66
  const res = await _isomorphicFetchWasmResponse(wasmUrl);
61
67
  return res.arrayBuffer();
62
68
  }
63
69
 
64
- /**
65
- * Compiles a WASM module from a URL, isomorphically (browser and Node.js).
66
- *
67
- * - Node + `file://`: uses `readFile` (Node `fetch` doesn't support `file://`)
68
- * - Browser + streaming: uses `WebAssembly.compileStreaming` (fastest path)
69
- * - Fallback: `fetch` + `arrayBuffer` + `WebAssembly.compile`
70
- */
71
- export async function isomorphicCompileWasm(wasmUrl: URL): Promise<WebAssembly.Module> {
72
- const isBrowser = isBrowserLike();
73
-
74
- if (isBrowser) {
75
- const res = await _isomorphicFetchWasmResponse(wasmUrl);
76
- if (typeof WebAssembly.compileStreaming === 'function') {
77
- return await WebAssembly.compileStreaming(res);
78
- }
79
- return WebAssembly.compile(await res.arrayBuffer());
80
- }
81
-
82
- const bytes = await _isomorphicReadWasmBytes(wasmUrl);
83
- // Safe cast: Node's Buffer is always backed by ArrayBuffer at runtime.
84
- return WebAssembly.compile(bytes);
85
- }
86
-
87
70
  /**
88
71
  * Fetches/reads a WASM module, verifies its SHA-256 digest, then compiles it.
89
72
  *
@@ -120,9 +103,19 @@ export async function isomorphicCompileWasmFromBase64(
120
103
 
121
104
  if (compressionFormat === undefined) {
122
105
  return WebAssembly.compile(bytes);
123
- } else {
106
+ }
107
+
108
+ // Preferred: stream through the platform DecompressionStream (native, zero-copy).
109
+ if (supportsDecompressionStream()) {
124
110
  const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream(compressionFormat));
125
111
  // compileStreaming requires Content-Type: application/wasm — supply it explicitly.
126
112
  return WebAssembly.compileStreaming(new Response(stream, { headers: { 'Content-Type': 'application/wasm' } }));
127
113
  }
114
+
115
+ // Fallback for runtimes without a usable DecompressionStream — older browsers
116
+ // (Firefox <113, Safari <16.4) and the Next.js Edge Runtime (whose stub throws on
117
+ // construction). Inflate in pure JS, then compile. Keeps the small compressed
118
+ // embedded payload working everywhere with no caller action.
119
+ const compressed = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes as ArrayBuffer);
120
+ return WebAssembly.compile(inflateDecompress(compressed, compressionFormat));
128
121
  }
@@ -237,11 +237,6 @@ export async function toTransportKeyPair(
237
237
  expectedType: 'Bytes | BytesHex',
238
238
  ...options,
239
239
  });
240
- assertRecordNonNullableProperty(value, 'tkmsVersion', name, {
241
- expectedType: 'string',
242
- ...options,
243
- });
244
-
245
240
  const rawPublicKey = value.publicKey;
246
241
  const rawPrivateKey = value.privateKey;
247
242
  const rawTkmsVersion = value.tkmsVersion;
@@ -16,6 +16,7 @@ import { assertIsUint64, assertIsUintNumber } from '../base/uint.js';
16
16
  import { assertIsKmsExtraData } from './kmsExtraData.js';
17
17
  import { kmsUserDecryptEip712Types } from './kmsUserDecryptEip712Types.js';
18
18
  import { isDeepEqual } from '../base/object.js';
19
+ import { kmsDelegatedUserDecryptEip712Types } from './kmsDelegatedUserDecryptEip712Types.js';
19
20
 
20
21
  ////////////////////////////////////////////////////////////////////////////////
21
22
 
@@ -121,7 +122,13 @@ export function _assertIsKmsUserDecryptEip712Base(
121
122
  assertIsKmsEip712Domain((value as Record<string, unknown>).domain, `${name}.domain`, options);
122
123
 
123
124
  assertRecordNonNullableProperty(value, 'types', name, options);
124
- if (!isDeepEqual(value.types, kmsUserDecryptEip712Types)) {
125
+
126
+ const expectedTypes =
127
+ primaryType === 'DelegatedUserDecryptRequestVerification'
128
+ ? kmsDelegatedUserDecryptEip712Types
129
+ : kmsUserDecryptEip712Types;
130
+
131
+ if (!isDeepEqual(value.types, expectedTypes)) {
125
132
  throw new Error('Unexpected KmsUserDecryptEip712Types');
126
133
  }
127
134
 
@@ -6,91 +6,167 @@ import type {
6
6
  TkmsModuleInfo,
7
7
  } from '../types.js';
8
8
  import type { KmsLibApi, TkmsVersion } from '../../../../wasm/tkms/KmsLibApi.js';
9
- import type { KmsAssetMetadata } from '../../../../wasm/tkms/loadKmsLib.js';
10
- import { isBrowserLike } from '../../../base/isomorphicWorker.js';
9
+ import type { KmsAssetMetadata, KmsAssets } from '../../../../wasm/tkms/loadKmsLib.js';
10
+ import { isBrowserLike } from '../../../base/environment.js';
11
11
  import { isomorphicCompileVerifiedWasm, isomorphicCompileWasmFromBase64 } from '../../../base/wasm.js';
12
12
  import { assertIsFhevmRuntime } from '../../../runtime/CoreFhevmRuntime-p.js';
13
- import { wasmBaseUrl } from '../../../../wasm/wasmBaseUrl.js';
14
13
  import { kmsAssetsWithVersion, loadKmsLib, loadKmsWasmBase64 } from '../../../../wasm/tkms/loadKmsLib.js';
14
+ import { isomorphicFileUrlExists } from '../../../base/isomorphicFs.js';
15
15
 
16
16
  ////////////////////////////////////////////////////////////////////////////////
17
17
 
18
- const ownerUidByVersion = new Map<TkmsVersion, string>();
19
- const cachedTkmsModulePromiseByVersion = new Map<TkmsVersion, Promise<KmsLibApi>>();
20
- const moduleInfoByVersion = new Map<TkmsVersion, TkmsModuleInfo>();
21
-
22
- ////////////////////////////////////////////////////////////////////////////////
23
-
24
- // Pure JS file (not compiled by tsc) — provides cross-platform base URL
25
- // for resolving WASM paths. Uses import.meta.url in ESM, __filename in CJS.
26
-
27
18
  // (Node only) Path relative to src/wasm/ where wasmBaseUrl is anchored
28
- const nodeDefaultLocateFile = (file: string): URL => {
19
+ const nodeDefaultLocateFile = async (file: string): Promise<URL> => {
20
+ // Pure JS file (not compiled by tsc) — provides cross-platform base URL
21
+ // for resolving WASM paths. Uses import.meta.url in ESM, __filename in CJS.
22
+ const { wasmBaseUrl } = await import('../../../../wasm/wasmBaseUrl.js');
29
23
  return new URL(`./tkms/${file}`, wasmBaseUrl);
30
24
  };
31
25
 
26
+ ////////////////////////////////////////////////////////////////////////////////
27
+
28
+ type KmsAssetResolution = 'user' | 'node' | 'none';
29
+
32
30
  type ResolvedKmsAsset = KmsAssetMetadata & {
33
31
  readonly url: URL | undefined;
32
+ readonly resolution: KmsAssetResolution;
34
33
  };
35
34
 
36
- function _resolveKmsAsset(asset: KmsAssetMetadata, locateFile: FhevmRuntimeConfig['locateFile']): ResolvedKmsAsset {
35
+ type ResolvedKmsAssets = {
36
+ readonly wasm: ResolvedKmsAsset;
37
+ };
38
+
39
+ ////////////////////////////////////////////////////////////////////////////////
40
+
41
+ async function _resolveSingleKmsAsset(
42
+ asset: KmsAssetMetadata,
43
+ locateFile: FhevmRuntimeConfig['locateFile'],
44
+ ): Promise<ResolvedKmsAsset> {
37
45
  let url: URL | undefined;
46
+ let resolution: KmsAssetResolution = 'none';
38
47
 
39
48
  if (locateFile !== undefined) {
40
- url = locateFile(asset.filename);
49
+ const located = locateFile(asset.filename) as unknown;
50
+ if (located === null || located === undefined) {
51
+ url = undefined;
52
+ } else if (located instanceof URL) {
53
+ url = located;
54
+ } else {
55
+ throw new TypeError(
56
+ `Invalid locateFile result for TKMS asset '${asset.filename}': expected URL, null, or undefined.`,
57
+ );
58
+ }
59
+ resolution = 'user';
41
60
  } else if (!isBrowserLike()) {
42
- url = nodeDefaultLocateFile(asset.localRelativePath);
61
+ url = await nodeDefaultLocateFile(asset.localRelativePath);
62
+ resolution = 'node';
63
+ }
64
+
65
+ return Object.freeze({ ...asset, url, resolution });
66
+ }
67
+
68
+ ////////////////////////////////////////////////////////////////////////////////
69
+
70
+ async function _resolveKmsAssets(
71
+ assets: KmsAssets,
72
+ locateFile: FhevmRuntimeConfig['locateFile'],
73
+ ): Promise<{ readonly wasm: ResolvedKmsAsset }> {
74
+ let wasm = await _resolveSingleKmsAsset(assets.wasm, locateFile);
75
+
76
+ // Only auto-derived ('node') file:// URLs need on-disk validation; the mode is
77
+ // shared across all assets, so checking one is enough to know the set's mode.
78
+ if (wasm.resolution === 'node') {
79
+ const allExist = await isomorphicFileUrlExists(wasm.url);
80
+ if (!allExist) {
81
+ wasm = Object.freeze({ ...wasm, url: undefined });
82
+ }
43
83
  }
44
84
 
45
- return Object.freeze({ ...asset, url });
85
+ return { wasm };
86
+ }
87
+
88
+ ////////////////////////////////////////////////////////////////////////////////
89
+
90
+ function _logResolvedKmsAssets(resolvedAssets: ResolvedKmsAssets, logger: FhevmRuntimeConfig['logger']): void {
91
+ const { wasm } = resolvedAssets;
92
+ if (wasm.resolution === 'user') {
93
+ logger?.debug(`resolve tkms wasm filename using 'locateFile' function: ${wasm.filename} -> url: ${wasm.url}`);
94
+ } else if (wasm.resolution === 'node') {
95
+ if (wasm.url === undefined) {
96
+ // Auto-derived assets were missing on disk (e.g. a bundler such as Turbopack
97
+ // relocated the package) and were cleared by _resolveTkmsAssets -> base64.
98
+ logger?.debug(
99
+ `tkms auto-derived assets not found on disk (bundler relocation?); using embedded base64 ` +
100
+ `(wasm: ${wasm.localRelativePath})`,
101
+ );
102
+ } else {
103
+ logger?.debug(`resolve tkms wasm local path: ${wasm.localRelativePath} -> url: ${wasm.url}`);
104
+ }
105
+ } else {
106
+ // 'none': browser zero-config (no 'locateFile', not Node) -> embedded base64.
107
+ logger?.debug(`resolve tkms assets using embedded base64 (browser, no 'locateFile')`);
108
+ }
46
109
  }
47
110
 
111
+ ////////////////////////////////////////////////////////////////////////////////
112
+
48
113
  type ResolvedTkmsModuleConfig = {
49
114
  readonly version: TkmsVersion;
50
- readonly wasm: ResolvedKmsAsset;
115
+ readonly assets: ResolvedKmsAssets;
51
116
  readonly logger: FhevmRuntimeConfig['logger'];
52
117
  };
53
118
 
54
119
  const resolvedTkmsModuleConfigByVersion = new Map<TkmsVersion, ResolvedTkmsModuleConfig>();
120
+ const resolvingTkmsModuleConfigPromiseByVersion = new Map<TkmsVersion, Promise<ResolvedTkmsModuleConfig>>();
55
121
 
56
122
  /**
57
123
  * @internal
58
- * Returns the existing resolved config, or resolves it from the runtime config.
124
+ * Returns the existing resolved config for a TKMS version, or resolves it from
125
+ * the runtime config.
59
126
  */
60
- function _getOrResolveTkmsModuleConfig(runtime: FhevmRuntime, tkmsVersion: TkmsVersion): ResolvedTkmsModuleConfig {
61
- let resolvedTkmsModuleConfig = resolvedTkmsModuleConfigByVersion.get(tkmsVersion);
127
+ async function _getOrResolveTkmsModuleConfig(
128
+ runtime: FhevmRuntime,
129
+ tkmsVersion: TkmsVersion,
130
+ ): Promise<ResolvedTkmsModuleConfig> {
131
+ const resolvedTkmsModuleConfig = resolvedTkmsModuleConfigByVersion.get(tkmsVersion);
62
132
  if (resolvedTkmsModuleConfig !== undefined) {
63
133
  return resolvedTkmsModuleConfig;
64
134
  }
65
135
 
66
- resolvedTkmsModuleConfig = _resolveTkmsModuleConfig(runtime.config, tkmsVersion);
67
-
68
- resolvedTkmsModuleConfigByVersion.set(tkmsVersion, resolvedTkmsModuleConfig);
136
+ let resolvingTkmsModuleConfigPromise = resolvingTkmsModuleConfigPromiseByVersion.get(tkmsVersion);
137
+ if (resolvingTkmsModuleConfigPromise === undefined) {
138
+ resolvingTkmsModuleConfigPromise = _resolveTkmsModuleConfig(runtime.config, tkmsVersion)
139
+ .then((cfg) => {
140
+ resolvedTkmsModuleConfigByVersion.set(tkmsVersion, cfg);
141
+ return cfg;
142
+ })
143
+ .catch((error: unknown) => {
144
+ resolvingTkmsModuleConfigPromiseByVersion.delete(tkmsVersion);
145
+ throw error;
146
+ });
147
+
148
+ resolvingTkmsModuleConfigPromiseByVersion.set(tkmsVersion, resolvingTkmsModuleConfigPromise);
149
+ }
69
150
 
70
- return resolvedTkmsModuleConfig;
151
+ return resolvingTkmsModuleConfigPromise;
71
152
  }
72
153
 
73
- function _resolveTkmsModuleConfig(parameters: FhevmRuntimeConfig, version: TkmsVersion): ResolvedTkmsModuleConfig {
154
+ async function _resolveTkmsModuleConfig(
155
+ parameters: FhevmRuntimeConfig,
156
+ version: TkmsVersion,
157
+ ): Promise<ResolvedTkmsModuleConfig> {
74
158
  const { locateFile } = parameters;
75
159
 
76
160
  const assets = kmsAssetsWithVersion(version);
77
- const wasm = _resolveKmsAsset(assets.wasm, locateFile);
78
161
 
79
- if (locateFile !== undefined) {
80
- parameters.logger?.debug(`resolve kms wasm filename: ${wasm.filename} -> url: ${wasm.url}`);
81
- } else {
82
- /*
83
- if run in Node only, use defaultLocateFile!
84
- */
85
- if (!isBrowserLike()) {
86
- parameters.logger?.debug(`resolve kms wasm local path: ${wasm.localRelativePath} -> url: ${wasm.url}`);
87
- }
88
- }
162
+ const resolvedAssets = await _resolveKmsAssets(assets, locateFile);
163
+
164
+ _logResolvedKmsAssets(resolvedAssets, parameters.logger);
89
165
 
90
166
  // if wasmUrl is undefined, use base64 code instead
91
167
  const cfg = {
92
168
  version,
93
- wasm,
169
+ assets: resolvedAssets,
94
170
  logger: parameters.logger,
95
171
  };
96
172
 
@@ -103,6 +179,12 @@ function _resolveTkmsModuleConfig(parameters: FhevmRuntimeConfig, version: TkmsV
103
179
  // initTkmsModule
104
180
  ////////////////////////////////////////////////////////////////////////////////
105
181
 
182
+ const ownerUidByVersion = new Map<TkmsVersion, string>();
183
+ const cachedTkmsModulePromiseByVersion = new Map<TkmsVersion, Promise<KmsLibApi>>();
184
+ const moduleInfoByVersion = new Map<TkmsVersion, TkmsModuleInfo>();
185
+
186
+ ////////////////////////////////////////////////////////////////////////////////
187
+
106
188
  type TkmsInitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
107
189
 
108
190
  type TkmsLibInitAsyncParameters = {
@@ -121,40 +203,47 @@ export async function initTkmsModule(runtime: FhevmRuntime, parameters: InitTkms
121
203
 
122
204
  ownerUidByVersion.set(parameters.tkmsVersion, runtime.uid);
123
205
 
124
- // Cache the TKMS initialization promise once and keep it cached even if it
125
- // rejects.
206
+ // Cache the whole initialization promise before the first await. Several
207
+ // clients may call initTkmsModule concurrently during startup.
126
208
  //
127
- // Retry is not supported:
128
- // -----------------------
129
- // The underlying KMS/WASM bindings
130
- // keep module-level state that cannot be reset reliably after a partial
131
- // initialization failure; later callers should observe the original error
132
- // instead of retrying against half-initialized state.
209
+ // Retry is not supported
133
210
 
134
211
  let cachedTkmsModulePromise = cachedTkmsModulePromiseByVersion.get(parameters.tkmsVersion);
135
212
  if (cachedTkmsModulePromise === undefined) {
136
- // resolve is sync
137
- const cfg = _getOrResolveTkmsModuleConfig(runtime, parameters.tkmsVersion);
138
- cachedTkmsModulePromise = _initTkmsModule(cfg);
213
+ cachedTkmsModulePromise = (async () => {
214
+ // resolve is async
215
+ const cfg = await _getOrResolveTkmsModuleConfig(runtime, parameters.tkmsVersion);
216
+ return await _initTkmsModule(cfg);
217
+ })();
218
+
139
219
  cachedTkmsModulePromiseByVersion.set(parameters.tkmsVersion, cachedTkmsModulePromise);
140
220
  }
141
221
 
142
222
  return cachedTkmsModulePromise;
143
223
  }
144
224
 
225
+ async function _compileWasmModule(cfg: ResolvedTkmsModuleConfig): Promise<WebAssembly.Module> {
226
+ let wasmModule;
227
+
228
+ if (cfg.assets.wasm.url !== undefined) {
229
+ cfg.logger?.debug(`compile tkms verified wasm at: ${cfg.assets.wasm.url}`);
230
+ wasmModule = await isomorphicCompileVerifiedWasm(cfg.assets.wasm.url, cfg.assets.wasm.sha256);
231
+ } else {
232
+ const { tkmsWasmBase64, tkmsWasmBase64CompressionFormat } = await loadKmsWasmBase64(cfg.version);
233
+ cfg.logger?.debug(
234
+ `compile tkms wasm from embedded base64 (compression:${tkmsWasmBase64CompressionFormat ?? 'none'})`,
235
+ );
236
+ wasmModule = await isomorphicCompileWasmFromBase64(tkmsWasmBase64, tkmsWasmBase64CompressionFormat);
237
+ }
238
+
239
+ return wasmModule;
240
+ }
241
+
145
242
  async function _initTkmsModule(cfg: ResolvedTkmsModuleConfig): Promise<KmsLibApi> {
146
243
  const kmsLib = await loadKmsLib(cfg.version);
147
244
 
148
245
  // Compile WASM module (see matrix in types.ts)
149
- let wasmModule;
150
- if (cfg.wasm.url !== undefined) {
151
- cfg.logger?.debug(`compile verified wasm at: ${cfg.wasm.url}`);
152
- wasmModule = await isomorphicCompileVerifiedWasm(cfg.wasm.url, cfg.wasm.sha256);
153
- } else {
154
- const { tkmsWasmBase64, tkmsWasmBase64IsGzipped } = await loadKmsWasmBase64(cfg.version);
155
- cfg.logger?.debug(`compile wasm from embedded base64 (compression:${tkmsWasmBase64IsGzipped})`);
156
- wasmModule = await isomorphicCompileWasmFromBase64(tkmsWasmBase64, tkmsWasmBase64IsGzipped ? 'gzip' : undefined);
157
- }
246
+ const wasmModule = await _compileWasmModule(cfg);
158
247
 
159
248
  const input: TkmsLibInitAsyncParameters = { module_or_path: wasmModule };
160
249
 
@@ -171,7 +260,7 @@ async function _initTkmsModule(cfg: ResolvedTkmsModuleConfig): Promise<KmsLibApi
171
260
  moduleInfoByVersion.set(
172
261
  cfg.version,
173
262
  Object.freeze({
174
- wasmUrl: cfg.wasm.url ? new URL(cfg.wasm.url) : undefined,
263
+ wasmUrl: cfg.assets.wasm.url ? new URL(cfg.assets.wasm.url) : undefined,
175
264
  version: wasmInfo.version,
176
265
  name: wasmInfo.name,
177
266
  memory,