sm-crypto-wasm 0.1.2 → 0.3.0

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.
@@ -0,0 +1,123 @@
1
+ /**
2
+ * SM9 密钥封装(KEM,GM/T 0044.4)。
3
+ *
4
+ * 两种封装入口:
5
+ * - `kemEncrypt(ke, id, klen, r?)`:用加密主私钥派生 Ppub-e(既有已发布接口)。
6
+ * - `kemEncapsulateWithMasterPublic(pub, id, klen, r?)`:直接用加密主公钥
7
+ * `[ke]P1`(发送方的典型形态;供 `hybrid.ts` 使用,不改变既有 `kemEncrypt`)。
8
+ *
9
+ * 解封装只有 `kemDecrypt(ke, id, encapsulation, klen)`:必须持有加密主私钥。
10
+ *
11
+ * spec/plan 对应:`docs/superpowers/plans/2026-10-16-sm2-sm9-hybrid-encryption.md`
12
+ * Task 4。
13
+ */
14
+ import { DecryptionError, InvalidKeyError } from '../runtime/errors.js';
15
+ import { getModule } from './core.js';
16
+ import { masterPrivateBytes, masterPublicBytes, putBytes, randomRHex, readHex, requireLen, toBytes, } from './input.js';
17
+ function requireKlen(klen) {
18
+ if (!Number.isInteger(klen) || klen <= 0)
19
+ throw new RangeError('sm9: klen 必须为正整数');
20
+ }
21
+ /**
22
+ * SM9 密钥封装(GM/T 0044.4):用接收方 id 与其加密主公钥 [ke]P1 封装一个会话密钥。
23
+ *
24
+ * @param ke 32 字节加密主私钥 hex(用于派生主公钥 [ke]P1)
25
+ * @param id 接收方标识
26
+ * @param klen 会话密钥字节长度(默认 32)
27
+ * @param r 32 字节随机数 hex(可选;不传则由宿主 CSPRNG 提供)
28
+ * @returns `{ key, encapsulation }`
29
+ */
30
+ export function kemEncrypt(ke, id, klen = 32, r) {
31
+ const keB = masterPrivateBytes(ke, 'ke');
32
+ const idB = toBytes(id, 'id');
33
+ const rB = r !== undefined ? toBytes(r, 'r') : toBytes(randomRHex(), 'r');
34
+ requireLen(rB, 32, 'r');
35
+ requireKlen(klen);
36
+ const { exports } = getModule();
37
+ const keP = exports.alloc(32);
38
+ const idP = exports.alloc(idB.length);
39
+ const rP = exports.alloc(32);
40
+ const c1P = exports.alloc(65);
41
+ const kP = exports.alloc(klen);
42
+ try {
43
+ putBytes(keB, keP);
44
+ putBytes(idB, idP);
45
+ putBytes(rB, rP);
46
+ const rc = exports.sm9KemEncryptOut(keP, idP, idB.length, rP, klen, c1P, kP);
47
+ if (rc !== 1)
48
+ throw new Error('sm9: kemEncrypt 失败(K 全 0,需换随机数重试)');
49
+ return { key: readHex(kP, klen), encapsulation: readHex(c1P, 65) };
50
+ }
51
+ finally {
52
+ exports.freeAll();
53
+ }
54
+ }
55
+ /**
56
+ * 用加密主公钥封装的内部入口(`hybridEncrypt` 使用)。
57
+ *
58
+ * 与 `kemEncrypt` 的区别只在密钥参数:这里不需要 KGC 的加密主私钥。
59
+ *
60
+ * @param masterPublicKey 64 字节加密主公钥 `[ke]P1`(点字节 / SPKI 主公钥 PEM)。
61
+ * @param id 接收方标识。
62
+ * @param klen 会话密钥字节长度(默认 32)。
63
+ * @param r 32 字节随机数 hex(可选)。
64
+ */
65
+ export function kemEncapsulateWithMasterPublic(masterPublicKey, id, klen = 32, r) {
66
+ const pub = masterPublicBytes(masterPublicKey, 'masterPublicKey');
67
+ if (pub.length !== 64) {
68
+ throw new InvalidKeyError(`sm9: masterPublicKey 必须是 64 字节加密主公钥,实际 ${pub.length}`);
69
+ }
70
+ const idB = toBytes(id, 'id');
71
+ const rB = r !== undefined ? toBytes(r, 'r') : toBytes(randomRHex(), 'r');
72
+ requireLen(rB, 32, 'r');
73
+ requireKlen(klen);
74
+ const { exports } = getModule();
75
+ const pubP = exports.alloc(64);
76
+ const idP = exports.alloc(idB.length);
77
+ const rP = exports.alloc(32);
78
+ const c1P = exports.alloc(65);
79
+ const kP = exports.alloc(klen);
80
+ try {
81
+ putBytes(pub, pubP);
82
+ putBytes(idB, idP);
83
+ putBytes(rB, rP);
84
+ const rc = exports.sm9KemEncryptPubOut(pubP, idP, idB.length, rP, klen, c1P, kP);
85
+ if (rc !== 1)
86
+ throw new Error('sm9: kemEncapsulate 失败(K 全 0,需换随机数重试)');
87
+ return { key: readHex(kP, klen), encapsulation: readHex(c1P, 65) };
88
+ }
89
+ finally {
90
+ exports.freeAll();
91
+ }
92
+ }
93
+ /**
94
+ * SM9 密钥解封装:用本端加密主私钥 ke 与 id 从封装点 C1 恢复会话密钥。
95
+ *
96
+ * @param encapsulation 65 字节封装点 C1 hex
97
+ * @returns 会话密钥 hex
98
+ */
99
+ export function kemDecrypt(ke, id, encapsulation, klen = 32) {
100
+ const keB = masterPrivateBytes(ke, 'ke');
101
+ const idB = toBytes(id, 'id');
102
+ const c1B = toBytes(encapsulation, 'encapsulation');
103
+ if (c1B.length !== 65)
104
+ throw new RangeError(`sm9: encapsulation 必须是 65 字节,实际 ${c1B.length}`);
105
+ requireKlen(klen);
106
+ const { exports } = getModule();
107
+ const keP = exports.alloc(32);
108
+ const idP = exports.alloc(idB.length);
109
+ const c1P = exports.alloc(65);
110
+ const kP = exports.alloc(klen);
111
+ try {
112
+ putBytes(keB, keP);
113
+ putBytes(idB, idP);
114
+ putBytes(c1B, c1P);
115
+ const rc = exports.sm9KemDecryptOut(keP, idP, idB.length, c1P, klen, kP);
116
+ if (rc !== 1)
117
+ throw new DecryptionError('sm9: kemDecrypt 失败(封装非法 / K 全 0)');
118
+ return readHex(kP, klen);
119
+ }
120
+ finally {
121
+ exports.freeAll();
122
+ }
123
+ }
package/dist/sm9/sm9.wasm CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sm-crypto-wasm",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "国密 SM2/SM3/SM4/SM9/ZUC 的 WASM 实现,Node 18+ 与现代浏览器均可使用,按算法分包懒加载。",
5
5
  "description:en": "SM2/SM3/SM4/SM9/ZUC (China crypto) implemented in AssemblyScript -> WASM. Node 18+ & modern browsers, per-algorithm lazy loading.",
6
6
  "license": "Apache-2.0",
@@ -92,6 +92,7 @@
92
92
  "format": "biome format --write src tests bench",
93
93
  "typecheck": "tsc --noEmit",
94
94
  "bench": "node --import tsx bench/run.ts",
95
+ "bench:cubelrti": "node --import tsx bench/compare-cubelrti.bench.ts",
95
96
  "docs:dev": "pnpm build && vitepress dev website",
96
97
  "docs:build": "pnpm build && vitepress build website",
97
98
  "docs:preview": "vitepress preview website",
@@ -106,6 +107,7 @@
106
107
  "happy-dom": "21.0.1",
107
108
  "mitata": "^1.0.34",
108
109
  "release-it": "^21.1.1",
110
+ "sm-crypto-v2": "1.15.1",
109
111
  "tsx": "^4.19.2",
110
112
  "typescript": "^7.0.2",
111
113
  "vitepress": "^1.6.4",