sm-crypto-wasm 0.1.0 → 0.1.2
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/README.md +154 -418
- package/dist/esbuild-plugin.d.ts +70 -0
- package/dist/esbuild-plugin.js +85 -0
- package/dist/runtime/loadWasm.browser.d.ts +39 -0
- package/dist/runtime/loadWasm.browser.js +78 -0
- package/dist/runtime/loadWasm.core.d.ts +45 -0
- package/dist/runtime/loadWasm.core.js +70 -0
- package/dist/runtime/loadWasm.d.ts +16 -27
- package/dist/runtime/loadWasm.js +28 -95
- package/dist/runtime/random.browser.d.ts +25 -0
- package/dist/runtime/random.browser.js +50 -0
- package/dist/runtime/random.core.d.ts +20 -0
- package/dist/runtime/random.core.js +35 -0
- package/dist/runtime/random.d.ts +16 -4
- package/dist/runtime/random.js +33 -28
- package/dist/sm2/index.js +39 -25
- package/dist/sm4/index.js +2 -2
- package/dist/sm9/index.js +8 -4
- package/dist/sm9/sm9.wasm +0 -0
- package/package.json +9 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* esbuild 插件:把 `.wasm` 资源放到浏览器产物**运行时能解析到的位置**。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么需要它
|
|
5
|
+
*
|
|
6
|
+
* 浏览器版的加载器用 `fetch(new URL('../<algo>/<algo>.wasm', import.meta.url))` 取
|
|
7
|
+
* wasm(这个写法 Vite 与 Webpack 5 都会自动归档资源并重写 URL)。**esbuild 不会** ——
|
|
8
|
+
* 无论 `--outfile` 还是 `--outdir`,它都不搬 `new URL` 指向的文件(实测 0.28.2)。
|
|
9
|
+
* 结果:构建能过,运行时 `fetch` 404。
|
|
10
|
+
*
|
|
11
|
+
* 本插件在 build 结束后把 `dist/<algo>/<algo>.wasm` 复制到该相对位置:
|
|
12
|
+
* 运行时 URL 是相对**产物 JS 文件**的 `../<algo>/<algo>.wasm`,所以目标基目录是
|
|
13
|
+
* **产物 JS 所在目录的上级**(由 esbuild 的 `outdir` / `outfile` 推导)。
|
|
14
|
+
*
|
|
15
|
+
* ## 用法
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { smCryptoWasm } from 'sm-crypto-wasm/esbuild-plugin';
|
|
19
|
+
*
|
|
20
|
+
* await esbuild.build({
|
|
21
|
+
* entryPoints: ['src/main.ts'],
|
|
22
|
+
* bundle: true,
|
|
23
|
+
* platform: 'browser',
|
|
24
|
+
* format: 'esm',
|
|
25
|
+
* outdir: 'dist/assets', // 产物 JS 落这里
|
|
26
|
+
* plugins: [smCryptoWasm()], // → wasm 复制到 dist/<algo>/<algo>.wasm
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* 建议把 `outdir` 指到子目录(如 `dist/assets`),这样 wasm 落在 `dist/<algo>/` 而不是
|
|
31
|
+
* 产物目录的上级。
|
|
32
|
+
*
|
|
33
|
+
* 只需用到部分算法时传 `algorithms` 少复制几个。
|
|
34
|
+
*/
|
|
35
|
+
/** 支持的算法名(与加载器白名单一致)。 */
|
|
36
|
+
declare const WASM_ALGOS: readonly ['sm2', 'sm3', 'sm4', 'sm9', 'zuc'];
|
|
37
|
+
export type WasmAlgo = (typeof WASM_ALGOS)[number];
|
|
38
|
+
/** `smCryptoWasm()` 的选项。 */
|
|
39
|
+
export interface SmCryptoWasmPluginOptions {
|
|
40
|
+
/** 只复制这些算法的 wasm(默认全部 5 个)。 */
|
|
41
|
+
algorithms?: readonly WasmAlgo[];
|
|
42
|
+
/** wasm 目标基目录。默认由 esbuild 的 `outdir` / `outfile` 推导(见模块注释)。 */
|
|
43
|
+
outDir?: string;
|
|
44
|
+
/** wasm 源目录。默认本包的 `dist/`(插件自身所在目录)。 */
|
|
45
|
+
sourceDir?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* esbuild 插件的**最小**结构。刻意不依赖 `esbuild` 的类型:这样本包不必把 esbuild
|
|
49
|
+
* 列为依赖,也不会因 esbuild 主版本升级而影响类型。
|
|
50
|
+
*/
|
|
51
|
+
export interface SmCryptoWasmPlugin {
|
|
52
|
+
name: string;
|
|
53
|
+
setup(build: {
|
|
54
|
+
initialOptions: {
|
|
55
|
+
outdir?: string;
|
|
56
|
+
outfile?: string;
|
|
57
|
+
};
|
|
58
|
+
onEnd: (cb: (result: {
|
|
59
|
+
errors: readonly unknown[];
|
|
60
|
+
}) => void | Promise<void>) => void;
|
|
61
|
+
}): void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 创建 esbuild 插件:build 成功后复制 wasm 资源。
|
|
65
|
+
*
|
|
66
|
+
* @param options 见 {@link SmCryptoWasmPluginOptions}。
|
|
67
|
+
* @returns 可直接放进 `plugins` 数组的插件对象。
|
|
68
|
+
*/
|
|
69
|
+
export declare function smCryptoWasm(options?: SmCryptoWasmPluginOptions): SmCryptoWasmPlugin;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* esbuild 插件:把 `.wasm` 资源放到浏览器产物**运行时能解析到的位置**。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么需要它
|
|
5
|
+
*
|
|
6
|
+
* 浏览器版的加载器用 `fetch(new URL('../<algo>/<algo>.wasm', import.meta.url))` 取
|
|
7
|
+
* wasm(这个写法 Vite 与 Webpack 5 都会自动归档资源并重写 URL)。**esbuild 不会** ——
|
|
8
|
+
* 无论 `--outfile` 还是 `--outdir`,它都不搬 `new URL` 指向的文件(实测 0.28.2)。
|
|
9
|
+
* 结果:构建能过,运行时 `fetch` 404。
|
|
10
|
+
*
|
|
11
|
+
* 本插件在 build 结束后把 `dist/<algo>/<algo>.wasm` 复制到该相对位置:
|
|
12
|
+
* 运行时 URL 是相对**产物 JS 文件**的 `../<algo>/<algo>.wasm`,所以目标基目录是
|
|
13
|
+
* **产物 JS 所在目录的上级**(由 esbuild 的 `outdir` / `outfile` 推导)。
|
|
14
|
+
*
|
|
15
|
+
* ## 用法
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { smCryptoWasm } from 'sm-crypto-wasm/esbuild-plugin';
|
|
19
|
+
*
|
|
20
|
+
* await esbuild.build({
|
|
21
|
+
* entryPoints: ['src/main.ts'],
|
|
22
|
+
* bundle: true,
|
|
23
|
+
* platform: 'browser',
|
|
24
|
+
* format: 'esm',
|
|
25
|
+
* outdir: 'dist/assets', // 产物 JS 落这里
|
|
26
|
+
* plugins: [smCryptoWasm()], // → wasm 复制到 dist/<algo>/<algo>.wasm
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* 建议把 `outdir` 指到子目录(如 `dist/assets`),这样 wasm 落在 `dist/<algo>/` 而不是
|
|
31
|
+
* 产物目录的上级。
|
|
32
|
+
*
|
|
33
|
+
* 只需用到部分算法时传 `algorithms` 少复制几个。
|
|
34
|
+
*/
|
|
35
|
+
import { copyFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
36
|
+
import { dirname, join, resolve } from 'node:path';
|
|
37
|
+
import { fileURLToPath } from 'node:url';
|
|
38
|
+
/** 支持的算法名(与加载器白名单一致)。 */
|
|
39
|
+
const WASM_ALGOS = ['sm2', 'sm3', 'sm4', 'sm9', 'zuc'];
|
|
40
|
+
/** 本包 `dist/` 的位置(编译后插件自身就在 `dist/` 下)。 */
|
|
41
|
+
const THIS_DIST_DIR = dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
/**
|
|
43
|
+
* 推导 wasm 的目标基目录。
|
|
44
|
+
*
|
|
45
|
+
* 运行时相对**产物 JS 文件**解析 `../<algo>/<algo>.wasm`,所以目标是产物 JS 目录的上级。
|
|
46
|
+
* esbuild 通常把入口产物放在 `outdir` 根(单入口时 `outbase` 就是入口所在目录)。
|
|
47
|
+
*/
|
|
48
|
+
function resolveTargetBase(opts) {
|
|
49
|
+
if (opts.outdir)
|
|
50
|
+
return resolve(opts.outdir, '..');
|
|
51
|
+
if (opts.outfile)
|
|
52
|
+
return resolve(dirname(opts.outfile), '..');
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 创建 esbuild 插件:build 成功后复制 wasm 资源。
|
|
57
|
+
*
|
|
58
|
+
* @param options 见 {@link SmCryptoWasmPluginOptions}。
|
|
59
|
+
* @returns 可直接放进 `plugins` 数组的插件对象。
|
|
60
|
+
*/
|
|
61
|
+
export function smCryptoWasm(options = {}) {
|
|
62
|
+
const algos = options.algorithms ?? WASM_ALGOS;
|
|
63
|
+
const sourceDir = options.sourceDir ?? THIS_DIST_DIR;
|
|
64
|
+
return {
|
|
65
|
+
name: 'sm-crypto-wasm-assets',
|
|
66
|
+
setup(build) {
|
|
67
|
+
build.onEnd((result) => {
|
|
68
|
+
// 构建失败时不产出资源,避免留下半套产物。
|
|
69
|
+
if (result.errors.length > 0)
|
|
70
|
+
return;
|
|
71
|
+
const targetBase = options.outDir ?? resolveTargetBase(build.initialOptions);
|
|
72
|
+
if (targetBase === undefined)
|
|
73
|
+
return; // `write: false` 等场景:没有落盘位置
|
|
74
|
+
for (const algo of algos) {
|
|
75
|
+
const from = join(sourceDir, algo, `${algo}.wasm`);
|
|
76
|
+
if (!existsSync(from))
|
|
77
|
+
continue; // 只装了部分算法时静默跳过
|
|
78
|
+
const to = join(targetBase, algo, `${algo}.wasm`);
|
|
79
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
80
|
+
copyFileSync(from, to);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM 加载器(**浏览器版**)。
|
|
3
|
+
*
|
|
4
|
+
* 打包时由 package.json 的 `browser` 字段替换 `loadWasm.js`。存在的唯一理由是:
|
|
5
|
+
* 浏览器的模块图里**不能出现 `node:*`** —— Webpack 5 / esbuild / Rspack 在解析期
|
|
6
|
+
* 就会因此构建失败(Vite 是少数会把 `node:*` 外部化成空 stub 的特例,不能依赖)。
|
|
7
|
+
* 本文件因此**零 `node:*` 引用**,共享逻辑在 `loadWasm.core.ts`。
|
|
8
|
+
*
|
|
9
|
+
* Node 环境下这个文件不会被用到(Node 用 `loadWasm.ts`)。
|
|
10
|
+
*/
|
|
11
|
+
import { type WasmAlgo, type WasmModule } from './loadWasm.core.js';
|
|
12
|
+
export type { WasmAlgo, WasmModule } from './loadWasm.core.js';
|
|
13
|
+
/**
|
|
14
|
+
* 异步加载并实例化 wasm。**这是业务代码的主入口**。
|
|
15
|
+
*
|
|
16
|
+
* 并发安全:同名的第二次调用复用第一次的 Promise,不会触发额外 instantiate。
|
|
17
|
+
* 调用前请确保已初始化(软件包文档里说明的 wasm 资源可被解析到)。
|
|
18
|
+
*
|
|
19
|
+
* @param name 算法名。
|
|
20
|
+
* @returns 解析后冻结的 WasmModule。
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadWasm(name: WasmAlgo): Promise<WasmModule>;
|
|
23
|
+
/**
|
|
24
|
+
* 同步加载:**浏览器端不可用**(`fetch` 本质上异步)。
|
|
25
|
+
*
|
|
26
|
+
* @param name 算法名。
|
|
27
|
+
* @throws 恒抛错,提示改用 `await loadWasm(name)`。
|
|
28
|
+
*/
|
|
29
|
+
export declare function loadWasmSync(name: WasmAlgo): WasmModule;
|
|
30
|
+
/**
|
|
31
|
+
* 获取已缓存的 wasm 引用(若无则初始化)。等价于 `loadWasm`,提供语义化别名。
|
|
32
|
+
*
|
|
33
|
+
* @param name 算法名。
|
|
34
|
+
*/
|
|
35
|
+
export declare function getCachedWasm(name: WasmAlgo): Promise<WasmModule>;
|
|
36
|
+
/**
|
|
37
|
+
* 清空缓存。仅供测试使用。
|
|
38
|
+
*/
|
|
39
|
+
export declare function _resetCacheForTests(): void;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM 加载器(**浏览器版**)。
|
|
3
|
+
*
|
|
4
|
+
* 打包时由 package.json 的 `browser` 字段替换 `loadWasm.js`。存在的唯一理由是:
|
|
5
|
+
* 浏览器的模块图里**不能出现 `node:*`** —— Webpack 5 / esbuild / Rspack 在解析期
|
|
6
|
+
* 就会因此构建失败(Vite 是少数会把 `node:*` 外部化成空 stub 的特例,不能依赖)。
|
|
7
|
+
* 本文件因此**零 `node:*` 引用**,共享逻辑在 `loadWasm.core.ts`。
|
|
8
|
+
*
|
|
9
|
+
* Node 环境下这个文件不会被用到(Node 用 `loadWasm.ts`)。
|
|
10
|
+
*/
|
|
11
|
+
import { instantiateFromBuffer, isWasmAlgo, memoizeLoad, rejectBadName, resetCacheForTests, } from './loadWasm.core.js';
|
|
12
|
+
/**
|
|
13
|
+
* 每个算法的 wasm URL。
|
|
14
|
+
*
|
|
15
|
+
* **显式列出**而不是用模板拼路径(`../${name}/${name}.wasm`):打包器只能静态识别
|
|
16
|
+
* `new URL('./literal.wasm', import.meta.url)` 这种写法,才会把 wasm 归档进产物并
|
|
17
|
+
* 重写 URL;动态拼接出来的路径打包器既不会复制文件、也不会改写,运行时必然 404。
|
|
18
|
+
*/
|
|
19
|
+
const WASM_URLS = {
|
|
20
|
+
sm2: new URL('../sm2/sm2.wasm', import.meta.url),
|
|
21
|
+
sm3: new URL('../sm3/sm3.wasm', import.meta.url),
|
|
22
|
+
sm4: new URL('../sm4/sm4.wasm', import.meta.url),
|
|
23
|
+
sm9: new URL('../sm9/sm9.wasm', import.meta.url),
|
|
24
|
+
zuc: new URL('../zuc/zuc.wasm', import.meta.url),
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* 取 wasm 字节(浏览器路径:`fetch` 同级资源,URL 由打包器解析)。
|
|
28
|
+
*
|
|
29
|
+
* @param name 算法名。
|
|
30
|
+
* @returns wasm 二进制 ArrayBuffer(总是一次新的读取)。
|
|
31
|
+
*/
|
|
32
|
+
async function fetchWasm(name) {
|
|
33
|
+
const url = WASM_URLS[name];
|
|
34
|
+
const res = await fetch(url);
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
throw new Error(`loadWasm: failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
return res.arrayBuffer();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 异步加载并实例化 wasm。**这是业务代码的主入口**。
|
|
42
|
+
*
|
|
43
|
+
* 并发安全:同名的第二次调用复用第一次的 Promise,不会触发额外 instantiate。
|
|
44
|
+
* 调用前请确保已初始化(软件包文档里说明的 wasm 资源可被解析到)。
|
|
45
|
+
*
|
|
46
|
+
* @param name 算法名。
|
|
47
|
+
* @returns 解析后冻结的 WasmModule。
|
|
48
|
+
*/
|
|
49
|
+
export function loadWasm(name) {
|
|
50
|
+
if (!isWasmAlgo(name))
|
|
51
|
+
rejectBadName(name);
|
|
52
|
+
return memoizeLoad(name, async (n) => instantiateFromBuffer(await fetchWasm(n)));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 同步加载:**浏览器端不可用**(`fetch` 本质上异步)。
|
|
56
|
+
*
|
|
57
|
+
* @param name 算法名。
|
|
58
|
+
* @throws 恒抛错,提示改用 `await loadWasm(name)`。
|
|
59
|
+
*/
|
|
60
|
+
export function loadWasmSync(name) {
|
|
61
|
+
if (!isWasmAlgo(name))
|
|
62
|
+
rejectBadName(name);
|
|
63
|
+
throw new Error('loadWasmSync: browser must await loadWasm() first');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 获取已缓存的 wasm 引用(若无则初始化)。等价于 `loadWasm`,提供语义化别名。
|
|
67
|
+
*
|
|
68
|
+
* @param name 算法名。
|
|
69
|
+
*/
|
|
70
|
+
export function getCachedWasm(name) {
|
|
71
|
+
return loadWasm(name);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 清空缓存。仅供测试使用。
|
|
75
|
+
*/
|
|
76
|
+
export function _resetCacheForTests() {
|
|
77
|
+
resetCacheForTests();
|
|
78
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM 加载器共享核心(Node 版与浏览器版共用)。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么要拆 core
|
|
5
|
+
*
|
|
6
|
+
* `node:*` 内置模块**不能出现在浏览器构建的模块图里**:打包器(Webpack 5 /
|
|
7
|
+
* esbuild / Rspack)在**解析期**就会因 `node:fs` 之类的裸模块报错中止,而
|
|
8
|
+
* tree-shaking 帮不上忙 —— 解析先于摇树。所以:
|
|
9
|
+
*
|
|
10
|
+
* - Node 侧(读文件)实现放 `loadWasm.ts`;
|
|
11
|
+
* - 浏览器侧(fetch)实现放 `loadWasm.browser.ts`;
|
|
12
|
+
* - 由 package.json 的 `browser` 字段在打包时做文件替换;
|
|
13
|
+
* - **本文件两者共用,且零 `node:*` 引用**。
|
|
14
|
+
*/
|
|
15
|
+
/** 算法白名单:确保非法 `name` 在到达文件系统 / 网络前被拒绝。 */
|
|
16
|
+
declare const WASM_ALGOS: readonly ['sm2', 'sm3', 'sm4', 'sm9', 'zuc'];
|
|
17
|
+
export type WasmAlgo = (typeof WASM_ALGOS)[number];
|
|
18
|
+
export declare function isWasmAlgo(name: unknown): name is WasmAlgo;
|
|
19
|
+
export declare function rejectBadName(name: unknown): never;
|
|
20
|
+
/** 加载完成后的 WASM 模块封装。`exports` 已类型化为 `WebAssembly.Exports`。 */
|
|
21
|
+
export interface WasmModule {
|
|
22
|
+
readonly exports: WebAssembly.Exports;
|
|
23
|
+
readonly memory: WebAssembly.Memory;
|
|
24
|
+
}
|
|
25
|
+
/** ASC 默认产物会导入 `env` 模块(abort / trace / __assert_fail)。 */
|
|
26
|
+
export declare const ASC_ENV: WebAssembly.Imports;
|
|
27
|
+
/**
|
|
28
|
+
* 同步实例化 wasm。失败抛 WebAssembly 自身的 CompileError / LinkError。
|
|
29
|
+
*
|
|
30
|
+
* @param buf wasm 字节。
|
|
31
|
+
*/
|
|
32
|
+
export declare function instantiateFromBuffer(buf: ArrayBuffer): WasmModule;
|
|
33
|
+
/**
|
|
34
|
+
* 按算法名记忆化加载:同名的第二次调用复用第一次的 Promise,并发安全
|
|
35
|
+
* (不会重复 instantiate)。
|
|
36
|
+
*
|
|
37
|
+
* @param name 算法名(调用方已校验)。
|
|
38
|
+
* @param loader 实际取字节 + 实例化的实现(Node / 浏览器各不相同)。
|
|
39
|
+
*/
|
|
40
|
+
export declare function memoizeLoad(name: WasmAlgo, loader: (name: WasmAlgo) => Promise<WasmModule>): Promise<WasmModule>;
|
|
41
|
+
/**
|
|
42
|
+
* 清空缓存。仅供测试使用 —— 单元测试需要测并发安全场景时必须能重置状态。
|
|
43
|
+
*/
|
|
44
|
+
export declare function resetCacheForTests(): void;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM 加载器共享核心(Node 版与浏览器版共用)。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么要拆 core
|
|
5
|
+
*
|
|
6
|
+
* `node:*` 内置模块**不能出现在浏览器构建的模块图里**:打包器(Webpack 5 /
|
|
7
|
+
* esbuild / Rspack)在**解析期**就会因 `node:fs` 之类的裸模块报错中止,而
|
|
8
|
+
* tree-shaking 帮不上忙 —— 解析先于摇树。所以:
|
|
9
|
+
*
|
|
10
|
+
* - Node 侧(读文件)实现放 `loadWasm.ts`;
|
|
11
|
+
* - 浏览器侧(fetch)实现放 `loadWasm.browser.ts`;
|
|
12
|
+
* - 由 package.json 的 `browser` 字段在打包时做文件替换;
|
|
13
|
+
* - **本文件两者共用,且零 `node:*` 引用**。
|
|
14
|
+
*/
|
|
15
|
+
/** 算法白名单:确保非法 `name` 在到达文件系统 / 网络前被拒绝。 */
|
|
16
|
+
const WASM_ALGOS = ['sm2', 'sm3', 'sm4', 'sm9', 'zuc'];
|
|
17
|
+
export function isWasmAlgo(name) {
|
|
18
|
+
return typeof name === 'string' && WASM_ALGOS.includes(name);
|
|
19
|
+
}
|
|
20
|
+
export function rejectBadName(name) {
|
|
21
|
+
const got = typeof name === 'string' ? `"${name}"` : typeof name;
|
|
22
|
+
throw new RangeError(`loadWasm: 未知算法 ${got},期望 ${WASM_ALGOS.map((a) => `"${a}"`).join(' | ')}`);
|
|
23
|
+
}
|
|
24
|
+
/** ASC 默认产物会导入 `env` 模块(abort / trace / __assert_fail)。 */
|
|
25
|
+
export const ASC_ENV = {
|
|
26
|
+
env: {
|
|
27
|
+
abort: () => {
|
|
28
|
+
throw new Error('wasm: abort');
|
|
29
|
+
},
|
|
30
|
+
trace: () => { },
|
|
31
|
+
__assert_fail: () => {
|
|
32
|
+
throw new Error('wasm: assert_fail');
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* 同步实例化 wasm。失败抛 WebAssembly 自身的 CompileError / LinkError。
|
|
38
|
+
*
|
|
39
|
+
* @param buf wasm 字节。
|
|
40
|
+
*/
|
|
41
|
+
export function instantiateFromBuffer(buf) {
|
|
42
|
+
const mod = new WebAssembly.Module(buf);
|
|
43
|
+
const inst = new WebAssembly.Instance(mod, ASC_ENV);
|
|
44
|
+
const exports = inst.exports;
|
|
45
|
+
const memory = exports.memory ?? new WebAssembly.Memory({ initial: 1 });
|
|
46
|
+
return Object.freeze({ exports, memory });
|
|
47
|
+
}
|
|
48
|
+
/** 模块缓存。key = 算法名,value = 已 memoize 的 Promise。 */
|
|
49
|
+
const cache = new Map();
|
|
50
|
+
/**
|
|
51
|
+
* 按算法名记忆化加载:同名的第二次调用复用第一次的 Promise,并发安全
|
|
52
|
+
* (不会重复 instantiate)。
|
|
53
|
+
*
|
|
54
|
+
* @param name 算法名(调用方已校验)。
|
|
55
|
+
* @param loader 实际取字节 + 实例化的实现(Node / 浏览器各不相同)。
|
|
56
|
+
*/
|
|
57
|
+
export function memoizeLoad(name, loader) {
|
|
58
|
+
let p = cache.get(name);
|
|
59
|
+
if (!p) {
|
|
60
|
+
p = loader(name);
|
|
61
|
+
cache.set(name, p);
|
|
62
|
+
}
|
|
63
|
+
return p;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 清空缓存。仅供测试使用 —— 单元测试需要测并发安全场景时必须能重置状态。
|
|
67
|
+
*/
|
|
68
|
+
export function resetCacheForTests() {
|
|
69
|
+
cache.clear();
|
|
70
|
+
}
|
|
@@ -1,31 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* WASM 加载器(**Node 版**)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 浏览器打包时,本文件会被 `loadWasm.browser.ts` 替换(package.json 的 `browser`
|
|
5
|
+
* 字段)—— 因为这里的 `node:fs` / `node:path` / `node:url` 会让 Webpack / esbuild /
|
|
6
|
+
* Rspack 在解析期直接构建失败。共享逻辑(类型、实例化、记忆化)在
|
|
7
|
+
* `loadWasm.core.ts`。**不要在 core 或 browser 版里 import 本文件。**
|
|
8
8
|
*
|
|
9
9
|
* 设计要点:
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* - **内存视图返回**:所有从 WASM 内存取出的字节都用 `slice()` 返回拷贝,
|
|
17
|
-
* 绝不让上层持有会因内存增长而失效的视图。Review-Focus #8。
|
|
18
|
-
*
|
|
19
|
-
* spec/plan 对应:plan Task 1;spec §7 跨运行时、§10 测试。
|
|
10
|
+
* - **按算法名称记忆化**:同名在并发调用下只实例化一次(见 core 的 `memoizeLoad`)。
|
|
11
|
+
* - **同步 / 异步并存**:业务 API 大量使用同步入口,但 WASM 加载必须异步;
|
|
12
|
+
* 这里同时提供 `loadWasm`(async)与 `loadWasmSync`(Node 专用,
|
|
13
|
+
* 每次独立读文件 + instantiate,与异步版不共享实例)。
|
|
14
|
+
* - **内存视图返回**:所有从 WASM 内存取出的字节都由上层 `slice()` 拷贝,
|
|
15
|
+
* 绝不让上层持有会因内存增长而失效的视图。
|
|
20
16
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export type WasmAlgo = (typeof WASM_ALGOS)[number];
|
|
24
|
-
/** 加载完成后的 WASM 模块封装。`exports` 已类型化为 `WebAssembly.Exports`。 */
|
|
25
|
-
export interface WasmModule {
|
|
26
|
-
readonly exports: WebAssembly.Exports;
|
|
27
|
-
readonly memory: WebAssembly.Memory;
|
|
28
|
-
}
|
|
17
|
+
import { type WasmAlgo, type WasmModule } from './loadWasm.core.js';
|
|
18
|
+
export type { WasmAlgo, WasmModule } from './loadWasm.core.js';
|
|
29
19
|
/**
|
|
30
20
|
* 异步加载并实例化 wasm。**这是业务代码的主入口**。
|
|
31
21
|
*
|
|
@@ -36,10 +26,10 @@ export interface WasmModule {
|
|
|
36
26
|
*/
|
|
37
27
|
export declare function loadWasm(name: WasmAlgo): Promise<WasmModule>;
|
|
38
28
|
/**
|
|
39
|
-
* 同步加载 wasm
|
|
29
|
+
* 同步加载 wasm(**Node 专用**)。浏览器路径不可用,会抛错。
|
|
40
30
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
31
|
+
* 每次调用独立读文件 + instantiate,**不**复用 `loadWasm` 的缓存实例
|
|
32
|
+
* (Node 的 Promise 无法被同步等待)。
|
|
43
33
|
*
|
|
44
34
|
* @param name 算法名。
|
|
45
35
|
* @returns 冻结的 WasmModule。
|
|
@@ -55,4 +45,3 @@ export declare function getCachedWasm(name: WasmAlgo): Promise<WasmModule>;
|
|
|
55
45
|
* 清空缓存。仅供测试使用 —— 单元测试需要测并发安全场景时必须能重置状态。
|
|
56
46
|
*/
|
|
57
47
|
export declare function _resetCacheForTests(): void;
|
|
58
|
-
export {};
|
package/dist/runtime/loadWasm.js
CHANGED
|
@@ -1,36 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* WASM 加载器(**Node 版**)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 浏览器打包时,本文件会被 `loadWasm.browser.ts` 替换(package.json 的 `browser`
|
|
5
|
+
* 字段)—— 因为这里的 `node:fs` / `node:path` / `node:url` 会让 Webpack / esbuild /
|
|
6
|
+
* Rspack 在解析期直接构建失败。共享逻辑(类型、实例化、记忆化)在
|
|
7
|
+
* `loadWasm.core.ts`。**不要在 core 或 browser 版里 import 本文件。**
|
|
8
8
|
*
|
|
9
9
|
* 设计要点:
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* - **内存视图返回**:所有从 WASM 内存取出的字节都用 `slice()` 返回拷贝,
|
|
17
|
-
* 绝不让上层持有会因内存增长而失效的视图。Review-Focus #8。
|
|
18
|
-
*
|
|
19
|
-
* spec/plan 对应:plan Task 1;spec §7 跨运行时、§10 测试。
|
|
10
|
+
* - **按算法名称记忆化**:同名在并发调用下只实例化一次(见 core 的 `memoizeLoad`)。
|
|
11
|
+
* - **同步 / 异步并存**:业务 API 大量使用同步入口,但 WASM 加载必须异步;
|
|
12
|
+
* 这里同时提供 `loadWasm`(async)与 `loadWasmSync`(Node 专用,
|
|
13
|
+
* 每次独立读文件 + instantiate,与异步版不共享实例)。
|
|
14
|
+
* - **内存视图返回**:所有从 WASM 内存取出的字节都由上层 `slice()` 拷贝,
|
|
15
|
+
* 绝不让上层持有会因内存增长而失效的视图。
|
|
20
16
|
*/
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
|
|
26
|
-
const WASM_ALGOS = ['sm2', 'sm3', 'sm4', 'sm9', 'zuc'];
|
|
27
|
-
function isWasmAlgo(name) {
|
|
28
|
-
return typeof name === 'string' && WASM_ALGOS.includes(name);
|
|
29
|
-
}
|
|
30
|
-
function rejectBadName(name) {
|
|
31
|
-
const got = typeof name === 'string' ? `"${name}"` : typeof name;
|
|
32
|
-
throw new RangeError(`loadWasm: 未知算法 ${got},期望 ${WASM_ALGOS.map((a) => `"${a}"`).join(' | ')}`);
|
|
33
|
-
}
|
|
17
|
+
import * as nodeFs from 'node:fs';
|
|
18
|
+
import * as nodeFsPromises from 'node:fs/promises';
|
|
19
|
+
import * as nodePath from 'node:path';
|
|
20
|
+
import * as nodeUrl from 'node:url';
|
|
21
|
+
import { instantiateFromBuffer, isWasmAlgo, memoizeLoad, rejectBadName, resetCacheForTests, } from './loadWasm.core.js';
|
|
34
22
|
/** 当前是否在 Node 进程内。CI / Vite SSR / 真浏览器都据此切换加载路径。 */
|
|
35
23
|
const isNode = typeof process !== 'undefined' && !!process.versions?.node;
|
|
36
24
|
/**
|
|
@@ -47,18 +35,18 @@ function wasmPath(name) {
|
|
|
47
35
|
// 1. cwd/dist(开发/单仓根目录模式)。这是首选路径;日志里
|
|
48
36
|
// 见到 "src/<name>/<name>.wasm" 即说明回退路径被命中,提示开发模式
|
|
49
37
|
// 下应当先 `pnpm build:wasm:<name>`。
|
|
50
|
-
const cwdPath = resolve(process.cwd(), 'dist', name, `${name}.wasm`);
|
|
51
|
-
if (existsSync(cwdPath))
|
|
38
|
+
const cwdPath = nodePath.resolve(process.cwd(), 'dist', name, `${name}.wasm`);
|
|
39
|
+
if (nodeFs.existsSync(cwdPath))
|
|
52
40
|
return cwdPath;
|
|
53
41
|
// 2. 源文件相对路径(生产产物 dist/runtime/loadWasm.js → ../<name>/<name>.wasm)。
|
|
54
42
|
let here;
|
|
55
43
|
try {
|
|
56
|
-
here = dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
here = nodePath.dirname(nodeUrl.fileURLToPath(import.meta.url));
|
|
57
45
|
}
|
|
58
46
|
catch {
|
|
59
47
|
here = process.cwd();
|
|
60
48
|
}
|
|
61
|
-
return resolve(here, '..', name, `${name}.wasm`);
|
|
49
|
+
return nodePath.resolve(here, '..', name, `${name}.wasm`);
|
|
62
50
|
}
|
|
63
51
|
/**
|
|
64
52
|
* 异步读取 wasm 字节(Node 路径)。
|
|
@@ -67,7 +55,7 @@ function wasmPath(name) {
|
|
|
67
55
|
* @returns wasm 二进制 ArrayBuffer;总是一个**拷贝**,不持有 fd 缓存。
|
|
68
56
|
*/
|
|
69
57
|
async function nodeReadWasm(name) {
|
|
70
|
-
const buf = await
|
|
58
|
+
const buf = await nodeFsPromises.readFile(wasmPath(name));
|
|
71
59
|
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
72
60
|
}
|
|
73
61
|
/**
|
|
@@ -77,53 +65,9 @@ async function nodeReadWasm(name) {
|
|
|
77
65
|
* @returns wasm 二进制 ArrayBuffer。
|
|
78
66
|
*/
|
|
79
67
|
function nodeReadWasmSync(name) {
|
|
80
|
-
const buf = readFileSync(wasmPath(name));
|
|
68
|
+
const buf = nodeFs.readFileSync(wasmPath(name));
|
|
81
69
|
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
82
70
|
}
|
|
83
|
-
/**
|
|
84
|
-
* 抽象出"按当前环境读 wasm 字节"。
|
|
85
|
-
*
|
|
86
|
-
* @param name 算法名。
|
|
87
|
-
* @returns ArrayBuffer 形式的 wasm 字节。
|
|
88
|
-
*/
|
|
89
|
-
async function loadArrayBuffer(name) {
|
|
90
|
-
if (isNode)
|
|
91
|
-
return nodeReadWasm(name);
|
|
92
|
-
// 浏览器:用 import.meta.url 解析同级 wasm 资源;打包器负责重写 URL。
|
|
93
|
-
const url = new URL(`../${name}/${name}.wasm`, import.meta.url);
|
|
94
|
-
const res = await fetch(url);
|
|
95
|
-
if (!res.ok) {
|
|
96
|
-
throw new Error(`loadWasm: failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
97
|
-
}
|
|
98
|
-
return res.arrayBuffer();
|
|
99
|
-
}
|
|
100
|
-
/** ASC 默认产物会导入 `env` 模块(abort / trace / __assert_fail)。
|
|
101
|
-
* 这些 stub 与 `loadWasm` 异步路径一致;统一到模块级常量以便 sync/async 共享。 */
|
|
102
|
-
const ASC_ENV = {
|
|
103
|
-
env: {
|
|
104
|
-
abort: () => {
|
|
105
|
-
throw new Error('wasm: abort');
|
|
106
|
-
},
|
|
107
|
-
trace: () => { },
|
|
108
|
-
__assert_fail: () => {
|
|
109
|
-
throw new Error('wasm: assert_fail');
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
/**
|
|
114
|
-
* 同步实例化 wasm(仅 Node)。失败抛 WebAssembly 自身的 CompileError / LinkError。
|
|
115
|
-
*
|
|
116
|
-
* @param buf wasm 字节。
|
|
117
|
-
*/
|
|
118
|
-
function instantiateFromBuffer(buf) {
|
|
119
|
-
const mod = new WebAssembly.Module(buf);
|
|
120
|
-
const inst = new WebAssembly.Instance(mod, ASC_ENV);
|
|
121
|
-
const exports = inst.exports;
|
|
122
|
-
const memory = exports.memory ?? new WebAssembly.Memory({ initial: 1 });
|
|
123
|
-
return Object.freeze({ exports, memory });
|
|
124
|
-
}
|
|
125
|
-
/** 模块缓存。key = 算法名,value = 已 memoize 的 Promise。 */
|
|
126
|
-
const cache = new Map();
|
|
127
71
|
/**
|
|
128
72
|
* 异步加载并实例化 wasm。**这是业务代码的主入口**。
|
|
129
73
|
*
|
|
@@ -135,21 +79,13 @@ const cache = new Map();
|
|
|
135
79
|
export function loadWasm(name) {
|
|
136
80
|
if (!isWasmAlgo(name))
|
|
137
81
|
rejectBadName(name);
|
|
138
|
-
|
|
139
|
-
if (!p) {
|
|
140
|
-
p = (async () => {
|
|
141
|
-
const buf = await loadArrayBuffer(name);
|
|
142
|
-
return instantiateFromBuffer(buf);
|
|
143
|
-
})();
|
|
144
|
-
cache.set(name, p);
|
|
145
|
-
}
|
|
146
|
-
return p;
|
|
82
|
+
return memoizeLoad(name, async (n) => instantiateFromBuffer(await nodeReadWasm(n)));
|
|
147
83
|
}
|
|
148
84
|
/**
|
|
149
|
-
* 同步加载 wasm
|
|
85
|
+
* 同步加载 wasm(**Node 专用**)。浏览器路径不可用,会抛错。
|
|
150
86
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
87
|
+
* 每次调用独立读文件 + instantiate,**不**复用 `loadWasm` 的缓存实例
|
|
88
|
+
* (Node 的 Promise 无法被同步等待)。
|
|
153
89
|
*
|
|
154
90
|
* @param name 算法名。
|
|
155
91
|
* @returns 冻结的 WasmModule。
|
|
@@ -160,8 +96,7 @@ export function loadWasmSync(name) {
|
|
|
160
96
|
}
|
|
161
97
|
if (!isWasmAlgo(name))
|
|
162
98
|
rejectBadName(name);
|
|
163
|
-
|
|
164
|
-
return instantiateFromBuffer(buf);
|
|
99
|
+
return instantiateFromBuffer(nodeReadWasmSync(name));
|
|
165
100
|
}
|
|
166
101
|
/**
|
|
167
102
|
* 获取已缓存的 wasm 引用(若无则初始化)。等价于 `loadWasm`,提供语义化别名。
|
|
@@ -175,7 +110,5 @@ export function getCachedWasm(name) {
|
|
|
175
110
|
* 清空缓存。仅供测试使用 —— 单元测试需要测并发安全场景时必须能重置状态。
|
|
176
111
|
*/
|
|
177
112
|
export function _resetCacheForTests() {
|
|
178
|
-
|
|
113
|
+
resetCacheForTests();
|
|
179
114
|
}
|
|
180
|
-
// 引用 readFile 以避免被 unused 误删(运行时仅在动态分支用到)。
|
|
181
|
-
void readFile;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主随机数包装(**浏览器版**)。
|
|
3
|
+
*
|
|
4
|
+
* 打包时由 package.json 的 `browser` 字段替换 `random.js`。存在的唯一理由是:
|
|
5
|
+
* `node:crypto` 会让 Webpack 5 / esbuild / Rspack 在解析期构建失败。
|
|
6
|
+
* 与宿主无关的拒绝采样逻辑在 `random.core.ts`。本文件**零 `node:*` 引用**。
|
|
7
|
+
*
|
|
8
|
+
* 安全姿态与 Node 版一致:**只认 `globalThis.crypto`,取不到就抛错
|
|
9
|
+
* (fail-closed),绝不降级到非密码学随机源。**
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* 取 `n` 字节 CSPRNG 随机数。
|
|
13
|
+
*
|
|
14
|
+
* @param n 字节数,范围 `[0, 65536]`(WebCrypto `getRandomValues` 的规范上限)。
|
|
15
|
+
* @returns 新分配的字节数组(拷贝)。
|
|
16
|
+
* @throws RangeError n 不在范围内或不是整数。
|
|
17
|
+
*/
|
|
18
|
+
export declare function randomBytes(n: number): Uint8Array;
|
|
19
|
+
/**
|
|
20
|
+
* 在 `[1, order)` 区间均匀取一个随机标量(用于 SM2 / SM9 椭圆曲线 k)。
|
|
21
|
+
*
|
|
22
|
+
* @param order 椭圆曲线子群阶(如 SM2 的 n、SM9 的 BN256 n)。
|
|
23
|
+
* @returns 满足 `1 ≤ k < order` 的大整数。
|
|
24
|
+
*/
|
|
25
|
+
export declare function randomScalarBelow(order: bigint): bigint;
|