sm-crypto-wasm 0.3.1 → 0.7.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.
Files changed (72) hide show
  1. package/README.md +70 -7
  2. package/dist/asn1/decode.d.ts +74 -0
  3. package/dist/asn1/decode.js +338 -0
  4. package/dist/asn1/encode.d.ts +68 -0
  5. package/dist/asn1/encode.js +275 -0
  6. package/dist/asn1/index.d.ts +11 -0
  7. package/dist/asn1/index.js +11 -0
  8. package/dist/asn1/oid.d.ts +22 -0
  9. package/dist/asn1/oid.js +107 -0
  10. package/dist/asn1/time.d.ts +39 -0
  11. package/dist/asn1/time.js +111 -0
  12. package/dist/asn1/types.d.ts +76 -0
  13. package/dist/asn1/types.js +71 -0
  14. package/dist/cms/errors.d.ts +13 -0
  15. package/dist/cms/errors.js +11 -0
  16. package/dist/cms/index.d.ts +13 -0
  17. package/dist/cms/index.js +10 -0
  18. package/dist/cms/oids.d.ts +17 -0
  19. package/dist/cms/oids.js +17 -0
  20. package/dist/cms/parse.d.ts +75 -0
  21. package/dist/cms/parse.js +326 -0
  22. package/dist/cms/sign.d.ts +43 -0
  23. package/dist/cms/sign.js +142 -0
  24. package/dist/cms/verify.d.ts +42 -0
  25. package/dist/cms/verify.js +173 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.js +3 -0
  28. package/dist/pem/asn1.d.ts +10 -69
  29. package/dist/pem/asn1.js +10 -292
  30. package/dist/pem/pkcs8.js +9 -1
  31. package/dist/pem/sm2.d.ts +16 -0
  32. package/dist/pem/sm2.js +37 -5
  33. package/dist/sm2/index.d.ts +69 -0
  34. package/dist/sm2/index.js +188 -0
  35. package/dist/sm2/sm2.wasm +0 -0
  36. package/dist/webcrypto/algorithms.d.ts +42 -0
  37. package/dist/webcrypto/algorithms.js +120 -0
  38. package/dist/webcrypto/cryptoKey.d.ts +64 -0
  39. package/dist/webcrypto/cryptoKey.js +109 -0
  40. package/dist/webcrypto/errors.d.ts +19 -0
  41. package/dist/webcrypto/errors.js +45 -0
  42. package/dist/webcrypto/index.d.ts +14 -0
  43. package/dist/webcrypto/index.js +11 -0
  44. package/dist/webcrypto/subtle.d.ts +68 -0
  45. package/dist/webcrypto/subtle.js +358 -0
  46. package/dist/x509/encode.d.ts +59 -0
  47. package/dist/x509/encode.js +175 -0
  48. package/dist/x509/errors.d.ts +14 -0
  49. package/dist/x509/errors.js +14 -0
  50. package/dist/x509/extensions.d.ts +27 -0
  51. package/dist/x509/extensions.js +215 -0
  52. package/dist/x509/index.d.ts +14 -0
  53. package/dist/x509/index.js +11 -0
  54. package/dist/x509/ip.d.ts +12 -0
  55. package/dist/x509/ip.js +101 -0
  56. package/dist/x509/issue.d.ts +44 -0
  57. package/dist/x509/issue.js +414 -0
  58. package/dist/x509/oids.d.ts +40 -0
  59. package/dist/x509/oids.js +52 -0
  60. package/dist/x509/parse.d.ts +24 -0
  61. package/dist/x509/parse.js +436 -0
  62. package/dist/x509/sha1.d.ts +9 -0
  63. package/dist/x509/sha1.js +72 -0
  64. package/dist/x509/sha256.d.ts +9 -0
  65. package/dist/x509/sha256.js +84 -0
  66. package/dist/x509/sig.d.ts +20 -0
  67. package/dist/x509/sig.js +77 -0
  68. package/dist/x509/types.d.ts +235 -0
  69. package/dist/x509/types.js +7 -0
  70. package/dist/x509/verify.d.ts +44 -0
  71. package/dist/x509/verify.js +66 -0
  72. package/package.json +15 -3
package/dist/pem/asn1.js CHANGED
@@ -1,297 +1,15 @@
1
1
  /**
2
- * 最小化 ASN.1 DER 解析器与编码器(BER/DER 子集)。
2
+ * 旧 ASN.1 入口(兼容层)。
3
3
  *
4
- * 本仓库只用到以下类型,刻意不做全 BER:
5
- * - INTEGER (0x02)、BIT STRING (0x03)、OCTET STRING (0x04)、NULL (0x05)
6
- * - OBJECT IDENTIFIER (0x06)、SEQUENCE (0x30, constructed)
4
+ * 实现已迁移到 `src/asn1/`(DER/BER 通用层)。本文件只保留历史导出名,
5
+ * 使 `src/pem/*` 与既有测试无需改动:
7
6
  *
8
- * 不支持 indefinite-length encoding(length byte = 0x80),国密 PKCS#8
9
- * 也用不到。长度只支持 definite-length(短/长两种格式)。
7
+ * - `parseDer` → `src/asn1/decode.ts`
8
+ * - `encodeDer` → `src/asn1/encode.ts` 的 `encode`
9
+ * - `asn1Int` / `asn1Octets` / `asn1Oid` / `asn1Null` / `asn1Seq` → 对应构造器
10
+ * - `encodeOid` / `decodeOid` / `asn1IntValue` → OID 与 INTEGER 解码
10
11
  *
11
- * spec/plan 对应:plan Task 4;spec §12.1 Q3(SM9 PKCS#8 + 自定义 OID)。
12
+ * spec/plan 对应:X.509/CMS plan 阶段 0「迁移」。
12
13
  */
13
- /** 抛错信息统一封装。 */
14
- export class Asn1Error extends Error {
15
- name = 'Asn1Error';
16
- }
17
- const TAG_INTEGER = 0x02;
18
- const _TAG_BIT_STRING = 0x03;
19
- const TAG_OCTET_STRING = 0x04;
20
- const TAG_NULL = 0x05;
21
- const TAG_OID = 0x06;
22
- const TAG_SEQUENCE = 0x30; // constructed
23
- /**
24
- * 解析一段 DER 字节。
25
- *
26
- * @param buf DER 字节。
27
- * @returns 解析后的根节点(通常为 SEQUENCE)。
28
- * @throws Asn1Error 输入截断、tag/length 不合法、不支持的构造。
29
- */
30
- export function parseDer(buf) {
31
- const reader = new Reader(buf);
32
- const node = reader.readNode();
33
- // 允许根节点之后留 0 字节尾巴;多余字节视为错误。
34
- if (reader.remaining() !== 0) {
35
- throw new Asn1Error(`parseDer: ${reader.remaining()} trailing bytes after root node`);
36
- }
37
- return node;
38
- }
39
- /**
40
- * 将 Asn1Node 编码回 DER 字节。**重新计算 length 与 raw**。
41
- *
42
- * @param node 待编码节点。
43
- * @returns DER 字节。
44
- */
45
- export function encodeDer(node) {
46
- // 值字节:primitive 直接取 value;constructed 递归子节点。
47
- const parts = [];
48
- if (node.children) {
49
- for (const child of node.children)
50
- parts.push(encodeDer(child));
51
- }
52
- const value = node.children ? concat(parts) : node.value;
53
- const lengthBytes = encodeLength(value.length);
54
- const tagByte = node.tag & 0xff;
55
- const out = new Uint8Array(1 + lengthBytes.length + value.length);
56
- out[0] = tagByte;
57
- for (let i = 0; i < lengthBytes.length; i++)
58
- out[1 + i] = lengthBytes[i] ?? 0;
59
- for (let i = 0; i < value.length; i++) {
60
- out[1 + lengthBytes.length + i] = value[i] ?? 0;
61
- }
62
- return out;
63
- }
64
- /**
65
- * 便捷构造函数:构造一个 INTEGER 节点。
66
- *
67
- * @param value 大端整数(可为负:负数用二进制补码表示)。
68
- */
69
- export function asn1Int(value) {
70
- const bytes = bigintToBytes(BigInt(value));
71
- const raw = concat([new Uint8Array([TAG_INTEGER]), encodeLength(bytes.length), bytes]);
72
- return {
73
- cls: 0,
74
- tag: TAG_INTEGER,
75
- length: bytes.length,
76
- value: bytes,
77
- raw,
78
- };
79
- }
80
- /** 便捷构造函数:构造一个 OCTET STRING 节点。 */
81
- export function asn1Octets(value) {
82
- const raw = concat([new Uint8Array([TAG_OCTET_STRING]), encodeLength(value.length), value]);
83
- return {
84
- cls: 0,
85
- tag: TAG_OCTET_STRING,
86
- length: value.length,
87
- value,
88
- raw,
89
- };
90
- }
91
- /** 便捷构造函数:构造一个 OID 节点(输入如 `"1.2.156.10197.1.100"`)。 */
92
- export function asn1Oid(oid) {
93
- const bytes = encodeOid(oid);
94
- const raw = concat([new Uint8Array([TAG_OID]), encodeLength(bytes.length), bytes]);
95
- return { cls: 0, tag: TAG_OID, length: bytes.length, value: bytes, raw };
96
- }
97
- /** 便捷构造函数:构造一个 NULL 节点。 */
98
- export function asn1Null() {
99
- const raw = new Uint8Array([TAG_NULL, 0x00]);
100
- return { cls: 0, tag: TAG_NULL, length: 0, value: new Uint8Array(0), raw };
101
- }
102
- /** 便捷构造函数:构造一个 SEQUENCE 节点。 */
103
- export function asn1Seq(children) {
104
- const parts = children.map((c) => c.raw);
105
- const value = concat(parts);
106
- const raw = concat([new Uint8Array([TAG_SEQUENCE]), encodeLength(value.length), value]);
107
- // tag 字段统一存 UNIVERSAL 编号 16(0x10),与 Reader 输出一致;
108
- // raw 首字节保留完整 0x30 以便序列化。
109
- return {
110
- cls: 0,
111
- tag: 0x10,
112
- length: value.length,
113
- value,
114
- children,
115
- raw,
116
- };
117
- }
118
- // ----------------- 内部:长度编码 -----------------
119
- function encodeLength(len) {
120
- if (len < 0 || !Number.isInteger(len)) {
121
- throw new Asn1Error(`encodeLength: bad length ${len}`);
122
- }
123
- if (len < 0x80)
124
- return new Uint8Array([len]);
125
- // 长格式:首个字节 0x80|n(n = 后续字节数),后续 big-endian 长度。
126
- const bytes = [];
127
- let v = len;
128
- while (v > 0) {
129
- bytes.unshift(v & 0xff);
130
- v >>= 8;
131
- }
132
- return new Uint8Array([0x80 | bytes.length, ...bytes]);
133
- }
134
- // ----------------- 内部:OID 编码 -----------------
135
- /**
136
- * OID 编码:第一个子标识符 = a*40 + b,后续每个子标识符按 7-bit 编码。
137
- *
138
- * 例:`1.2.156.10197.1.100` → [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x01, 0x01, 0x64]
139
- * 1*40+2 = 42 = 0x2a;156、10197、1、100 都用 base-128 编码。
140
- *
141
- * @param oid 点分 OID 字符串。
142
- * @returns OID 内容字节(不含 tag 和 length)。
143
- */
144
- export function encodeOid(oid) {
145
- const parts = oid.split('.').map((s) => parseInt(s, 10));
146
- if (parts.length < 2 || parts.some((n) => !Number.isFinite(n) || n < 0)) {
147
- throw new Asn1Error(`encodeOid: bad oid "${oid}"`);
148
- }
149
- const first = (parts[0] ?? 0) * 40 + (parts[1] ?? 0);
150
- const out = [first];
151
- for (let i = 2; i < parts.length; i++) {
152
- out.push(...encodeBase128(parts[i] ?? 0));
153
- }
154
- return new Uint8Array(out);
155
- }
156
- /** 反向解码。 */
157
- export function decodeOid(bytes) {
158
- if (bytes.length === 0)
159
- throw new Asn1Error('decodeOid: empty');
160
- const first = bytes[0] ?? 0;
161
- const a = Math.floor(first / 40);
162
- const b = first % 40;
163
- const parts = [a, b];
164
- let value = 0;
165
- for (let i = 1; i < bytes.length; i++) {
166
- const octet = bytes[i] ?? 0;
167
- value = (value << 7) | (octet & 0x7f);
168
- if ((octet & 0x80) === 0) {
169
- parts.push(value);
170
- value = 0;
171
- }
172
- }
173
- if (value !== 0)
174
- throw new Asn1Error('decodeOid: truncated');
175
- return parts.join('.');
176
- }
177
- function encodeBase128(n) {
178
- if (n < 0 || !Number.isInteger(n)) {
179
- throw new Asn1Error(`encodeBase128: bad ${n}`);
180
- }
181
- if (n < 0x80)
182
- return [n];
183
- const out = [];
184
- let v = n;
185
- do {
186
- out.unshift((v & 0x7f) | 0x80);
187
- v >>= 7;
188
- } while (v > 0);
189
- out[out.length - 1] = (out[out.length - 1] ?? 0) & 0x7f;
190
- return out;
191
- }
192
- // ----------------- 内部:大端整数 -----------------
193
- function bigintToBytes(n) {
194
- if (n === 0n)
195
- return new Uint8Array([0]);
196
- const negative = n < 0n;
197
- let v = negative ? -n : n;
198
- const tmp = [];
199
- while (v > 0n) {
200
- tmp.unshift(Number(v & 0xffn));
201
- v >>= 8n;
202
- }
203
- // 负数采用补码:高位加 0xff 让最高位为 1。
204
- if (negative) {
205
- // 简化处理:本仓库不需要负 INTEGER。
206
- throw new Asn1Error('bigintToBytes: negative integers not supported');
207
- }
208
- // 正数:补 0x00 前缀以免被当成负数(最高位为 1 时必须)。
209
- if ((tmp[0] ?? 0) & 0x80)
210
- tmp.unshift(0);
211
- return new Uint8Array(tmp);
212
- }
213
- /** 读一个 INTEGER 返回 bigint。 */
214
- export function asn1IntValue(node) {
215
- if (node.tag !== TAG_INTEGER) {
216
- throw new Asn1Error(`asn1IntValue: tag ${node.tag}, expected INTEGER`);
217
- }
218
- let v = 0n;
219
- for (let i = 0; i < node.value.length; i++) {
220
- v = (v << 8n) | BigInt(node.value[i] ?? 0);
221
- }
222
- return v;
223
- }
224
- // ----------------- 内部:Reader -----------------
225
- class Reader {
226
- buf;
227
- pos = 0;
228
- constructor(buf) {
229
- this.buf = buf;
230
- }
231
- remaining() {
232
- return this.buf.length - this.pos;
233
- }
234
- take(n) {
235
- if (this.pos + n > this.buf.length) {
236
- throw new Asn1Error(`Reader: truncated, need ${n} bytes at pos ${this.pos}`);
237
- }
238
- const out = this.buf.subarray(this.pos, this.pos + n);
239
- this.pos += n;
240
- return out;
241
- }
242
- readTag() {
243
- if (this.remaining() < 1)
244
- throw new Asn1Error('Reader: empty tag');
245
- return this.buf[this.pos++] ?? 0;
246
- }
247
- readLength() {
248
- if (this.remaining() < 1)
249
- throw new Asn1Error('Reader: empty length');
250
- const first = this.buf[this.pos++] ?? 0;
251
- if ((first & 0x80) === 0)
252
- return first;
253
- const n = first & 0x7f;
254
- if (n === 0)
255
- throw new Asn1Error('Reader: indefinite length not supported');
256
- if (this.remaining() < n)
257
- throw new Asn1Error('Reader: truncated long length');
258
- let len = 0;
259
- for (let i = 0; i < n; i++) {
260
- len = (len << 8) | (this.buf[this.pos++] ?? 0);
261
- }
262
- return len;
263
- }
264
- readNode() {
265
- const tagStart = this.pos;
266
- const tag = this.readTag();
267
- const length = this.readLength();
268
- const cls = (tag & 0xc0) >> 6;
269
- const tagNum = tag & 0x1f;
270
- const valueStart = this.pos;
271
- const value = this.take(length);
272
- const raw = this.buf.subarray(tagStart, valueStart + length);
273
- const constructed = (tag & 0x20) !== 0;
274
- if (constructed) {
275
- // SEQUENCE 等:递归子节点。
276
- const inner = new Reader(value);
277
- const children = [];
278
- while (inner.remaining() > 0) {
279
- children.push(inner.readNode());
280
- }
281
- return { cls, tag: tagNum, length, value, children, raw };
282
- }
283
- return { cls, tag: tagNum, length, value, raw };
284
- }
285
- }
286
- function concat(parts) {
287
- let total = 0;
288
- for (const p of parts)
289
- total += p.length;
290
- const out = new Uint8Array(total);
291
- let off = 0;
292
- for (const p of parts) {
293
- out.set(p, off);
294
- off += p.length;
295
- }
296
- return out;
297
- }
14
+ export { decodeOid, encode as encodeDer, encodeOid, integer as asn1Int, nullValue as asn1Null, octetString as asn1Octets, oid as asn1Oid, parseDer, readInteger as asn1IntValue, seq as asn1Seq, } from '../asn1/index.js';
15
+ export { Asn1Error } from '../asn1/types.js';
package/dist/pem/pkcs8.js CHANGED
@@ -155,7 +155,15 @@ function bitStringNode(value) {
155
155
  for (let i = 0; i < value.length; i++) {
156
156
  raw[1 + lengthBytes.length + i] = value[i] ?? 0;
157
157
  }
158
- return { cls: 0, tag: 0x03, length: value.length, value, raw };
158
+ return {
159
+ cls: 0,
160
+ tag: 0x03,
161
+ constructed: false,
162
+ indefinite: false,
163
+ length: value.length,
164
+ value,
165
+ raw,
166
+ };
159
167
  }
160
168
  function encodeLen(n) {
161
169
  if (n < 0x80)
package/dist/pem/sm2.d.ts CHANGED
@@ -37,12 +37,24 @@ export declare const OID_SM2_CURVE = "1.2.156.10197.1.301";
37
37
  * @throws InvalidKeyError PEM/DER 结构非法、或曲线不是 SM2。
38
38
  */
39
39
  export declare function parseSm2PrivateKeyPem(pem: string): Uint8Array;
40
+ /**
41
+ * 解析 SM2 私钥 DER(PKCS#8 或 SEC1),返回 32 字节私钥 d。
42
+ *
43
+ * WebCrypto 的 `importKey('pkcs8', ...)` 使用这个入口。
44
+ */
45
+ export declare function parseSm2PrivateKeyDer(der: Uint8Array): Uint8Array;
40
46
  /**
41
47
  * 解析 SM2 公钥 PEM,返回 65 字节未压缩点 `04 || X || Y`。
42
48
  *
43
49
  * @throws InvalidKeyError PEM/DER 结构非法、曲线不是 SM2、或点不是未压缩形式。
44
50
  */
45
51
  export declare function parseSm2PublicKeyPem(pem: string): Uint8Array;
52
+ /**
53
+ * 解析 SM2 公钥 DER(SPKI),返回 65 字节未压缩点。
54
+ *
55
+ * WebCrypto 的 `importKey('spki', ...)` 使用这个入口。
56
+ */
57
+ export declare function parseSm2PublicKeyDer(der: Uint8Array): Uint8Array;
46
58
  /**
47
59
  * 编码 SM2 私钥为 PKCS#8 PEM(`BEGIN PRIVATE KEY`)。
48
60
  *
@@ -50,9 +62,13 @@ export declare function parseSm2PublicKeyPem(pem: string): Uint8Array;
50
62
  * @param pub 可选公钥(64 或 65 字节);给出则内嵌到 ECPrivateKey 的 `[1]`。
51
63
  */
52
64
  export declare function encodeSm2PrivateKeyPem(d: string | Uint8Array, pub?: string | Uint8Array): string;
65
+ /** 编码 SM2 私钥为 PKCS#8 DER。 */
66
+ export declare function encodeSm2PrivateKeyDer(d: string | Uint8Array, pub?: string | Uint8Array): Uint8Array;
53
67
  /**
54
68
  * 编码 SM2 公钥为 SPKI PEM(`BEGIN PUBLIC KEY`)。
55
69
  *
56
70
  * @param pub 64(X || Y)或 65(04 || X || Y)字节公钥。
57
71
  */
58
72
  export declare function encodeSm2PublicKeyPem(pub: string | Uint8Array): string;
73
+ /** 编码 SM2 公钥为 SPKI DER。 */
74
+ export declare function encodeSm2PublicKeyDer(pub: string | Uint8Array): Uint8Array;
package/dist/pem/sm2.js CHANGED
@@ -47,7 +47,14 @@ const CLS_CONTEXT = 2;
47
47
  * @throws InvalidKeyError PEM/DER 结构非法、或曲线不是 SM2。
48
48
  */
49
49
  export function parseSm2PrivateKeyPem(pem) {
50
- const der = pemToBytes(pem, 'sm2.privateKey');
50
+ return parseSm2PrivateKeyDer(pemToBytes(pem, 'sm2.privateKey'));
51
+ }
52
+ /**
53
+ * 解析 SM2 私钥 DER(PKCS#8 或 SEC1),返回 32 字节私钥 d。
54
+ *
55
+ * WebCrypto 的 `importKey('pkcs8', ...)` 使用这个入口。
56
+ */
57
+ export function parseSm2PrivateKeyDer(der) {
51
58
  const top = parseOrThrow(der, 'parseSm2PrivateKeyPem');
52
59
  const kids = top.children;
53
60
  if (top.tag !== TAG_SEQUENCE || !kids) {
@@ -92,7 +99,14 @@ export function parseSm2PrivateKeyPem(pem) {
92
99
  * @throws InvalidKeyError PEM/DER 结构非法、曲线不是 SM2、或点不是未压缩形式。
93
100
  */
94
101
  export function parseSm2PublicKeyPem(pem) {
95
- const der = pemToBytes(pem, 'sm2.publicKey');
102
+ return parseSm2PublicKeyDer(pemToBytes(pem, 'sm2.publicKey'));
103
+ }
104
+ /**
105
+ * 解析 SM2 公钥 DER(SPKI),返回 65 字节未压缩点。
106
+ *
107
+ * WebCrypto 的 `importKey('spki', ...)` 使用这个入口。
108
+ */
109
+ export function parseSm2PublicKeyDer(der) {
96
110
  const top = parseOrThrow(der, 'parseSm2PublicKeyPem');
97
111
  const kids = top.children;
98
112
  if (top.tag !== TAG_SEQUENCE || !kids || kids.length < 2) {
@@ -118,6 +132,10 @@ export function parseSm2PublicKeyPem(pem) {
118
132
  * @param pub 可选公钥(64 或 65 字节);给出则内嵌到 ECPrivateKey 的 `[1]`。
119
133
  */
120
134
  export function encodeSm2PrivateKeyPem(d, pub) {
135
+ return bytesToPem(encodeSm2PrivateKeyDer(d, pub), 'PRIVATE KEY');
136
+ }
137
+ /** 编码 SM2 私钥为 PKCS#8 DER。 */
138
+ export function encodeSm2PrivateKeyDer(d, pub) {
121
139
  const scalar = normalizeScalar(d);
122
140
  const ecChildren = [
123
141
  asn1Int(1),
@@ -136,7 +154,7 @@ export function encodeSm2PrivateKeyPem(d, pub) {
136
154
  asn1Seq([asn1Oid(OID_ID_EC_PUBLIC_KEY), asn1Oid(OID_SM2_CURVE)]),
137
155
  asn1Octets(ec.raw),
138
156
  ]);
139
- return bytesToPem(pkcs8.raw, 'PRIVATE KEY');
157
+ return pkcs8.raw;
140
158
  }
141
159
  /**
142
160
  * 编码 SM2 公钥为 SPKI PEM(`BEGIN PUBLIC KEY`)。
@@ -144,6 +162,10 @@ export function encodeSm2PrivateKeyPem(d, pub) {
144
162
  * @param pub 64(X || Y)或 65(04 || X || Y)字节公钥。
145
163
  */
146
164
  export function encodeSm2PublicKeyPem(pub) {
165
+ return bytesToPem(encodeSm2PublicKeyDer(pub), 'PUBLIC KEY');
166
+ }
167
+ /** 编码 SM2 公钥为 SPKI DER。 */
168
+ export function encodeSm2PublicKeyDer(pub) {
147
169
  const point = normalizeUncompressed(pub);
148
170
  const bits = new Uint8Array(point.length + 1);
149
171
  bits.set(point, 1);
@@ -151,7 +173,7 @@ export function encodeSm2PublicKeyPem(pub) {
151
173
  asn1Seq([asn1Oid(OID_ID_EC_PUBLIC_KEY), asn1Oid(OID_SM2_CURVE)]),
152
174
  bitStringNode(bits),
153
175
  ]);
154
- return bytesToPem(spki.raw, 'PUBLIC KEY');
176
+ return spki.raw;
155
177
  }
156
178
  // ---------- 内部 ----------
157
179
  function parseOrThrow(der, where) {
@@ -229,7 +251,15 @@ function normalizeUncompressed(pub) {
229
251
  /** 构造 BIT STRING 节点(value 需自带首字节「未使用位数」)。 */
230
252
  function bitStringNode(value) {
231
253
  const raw = concat([new Uint8Array([TAG_BIT_STRING]), encodeLen(value.length), value]);
232
- return { cls: 0, tag: TAG_BIT_STRING, length: value.length, value, raw };
254
+ return {
255
+ cls: 0,
256
+ tag: TAG_BIT_STRING,
257
+ constructed: false,
258
+ indefinite: false,
259
+ length: value.length,
260
+ value,
261
+ raw,
262
+ };
233
263
  }
234
264
  /** 构造 [n] EXPLICIT 包装节点。 */
235
265
  function explicit(tagNum, inner) {
@@ -237,6 +267,8 @@ function explicit(tagNum, inner) {
237
267
  return {
238
268
  cls: CLS_CONTEXT,
239
269
  tag: tagNum,
270
+ constructed: true,
271
+ indefinite: false,
240
272
  length: inner.raw.length,
241
273
  value: inner.raw,
242
274
  children: [inner],
@@ -120,12 +120,81 @@ export declare function createSignaturePointPool(n: number): Promise<SignaturePo
120
120
  * @returns 64 字节签名(r || s)的 hex。
121
121
  */
122
122
  export declare function sign(privateKey: string | Uint8Array, message: string | Uint8Array, opts?: SignOptions): string;
123
+ /**
124
+ * SM2 签名(自定义用户标识)。
125
+ *
126
+ * 与 `sign` 的区别仅在于 Z 值使用 `userID` 计算。X.509 签发需要它:
127
+ * OpenSSL 的 X.509 路径默认使用空标识,GM/T 证书使用 `1234567812345678`。
128
+ *
129
+ * 不支持 `pointPool`(签发场景不需要;需要时用 `sign` 的默认标识路径)。
130
+ *
131
+ * @param privateKey 32 字节私钥(hex / Uint8Array / PEM)。
132
+ * @param message 待签名消息。
133
+ * @param userID 用户标识(可为空数组)。
134
+ * @returns 64 字节签名 `r ‖ s` 的 hex。
135
+ */
136
+ export declare function signWithID(privateKey: string | Uint8Array, message: string | Uint8Array, userID: Uint8Array): string;
123
137
  /**
124
138
  * SM2 验签。
125
139
  *
126
140
  * @returns true 有效。
127
141
  */
128
142
  export declare function verify(publicKey: string | Uint8Array, message: string | Uint8Array, signature: string | Uint8Array): boolean;
143
+ /**
144
+ * SM2 验签(自定义用户标识)。
145
+ *
146
+ * 与 `verify` 的区别仅在于 Z 值使用 `userID` 计算。X.509 证书签名需要它:
147
+ * GM/T 证书使用默认标识 `1234567812345678`,而 OpenSSL 的 X.509 路径
148
+ * 默认使用空标识。
149
+ *
150
+ * @param publicKey 64/65 字节公钥。
151
+ * @param message 消息。
152
+ * @param signature 64 字节 `r ‖ s`。
153
+ * @param userID 用户标识(可为空数组)。
154
+ * @returns true 有效。
155
+ */
156
+ export declare function verifyWithID(publicKey: string | Uint8Array, message: string | Uint8Array, signature: string | Uint8Array, userID: Uint8Array): boolean;
157
+ /** 多槽公钥缓存的配置。 */
158
+ export interface PreparedKeyCacheOptions {
159
+ /** 槽位上限,1..8,默认 8。 */
160
+ capacity?: number;
161
+ }
162
+ /**
163
+ * 配置多槽公钥缓存(默认 8 槽,满了按 LRU 淘汰)。
164
+ *
165
+ * 缩小容量不会立即清除高槽位,只是之后的查找与 `getPreparedPublicKeys` 不再考虑它们;
166
+ * 需要释放时调用 `clearPreparedPublicKeys()`。wasm 未加载时只记录,首次
167
+ * `preparePublicKey` 会应用。
168
+ */
169
+ export declare function configurePreparedKeyCache(opts?: PreparedKeyCacheOptions): void;
170
+ /**
171
+ * 为一对**固定公钥**预计算 comb 表(默认 8 槽,满了 LRU 淘汰)。
172
+ *
173
+ * 之后 `encrypt` / `kemEncrypt` / `hybridEncrypt` 对该公钥会把 `k·P` 从 wNAF
174
+ * (256 次倍点)切到 comb(52 次倍点):本机端到端约 486 → 260 µs。建表一次性
175
+ * 约 280 µs,**约 1.2 次加密回本**。
176
+ *
177
+ * **只有显式 prepare 才会建表**:`encrypt` 命中就走 comb,不命中直接回退 wNAF,
178
+ * 绝不隐式建表 —— 否则外部可控的公钥能反复触发建表,形成放大面。
179
+ *
180
+ * 公钥是公开信息,缓存它不引入新的密钥材料驻留;8 槽共占 wasm 永久区固定 24 KiB。
181
+ *
182
+ * @param publicKey 64/65 字节公钥(hex / Uint8Array / PEM)。
183
+ * @throws {InvalidPublicKeyError} 公钥不在 SM2 曲线上。
184
+ */
185
+ export declare function preparePublicKey(publicKey: string | Uint8Array): Promise<void>;
186
+ /** 清空所有公钥预计算槽,之后的 `encrypt` 全部回退到 wNAF。未加载 wasm 时也安全。 */
187
+ export declare function clearPreparedPublicKeys(): void;
188
+ /** @deprecated 用 `clearPreparedPublicKeys()`(保留单数别名)。 */
189
+ export declare function clearPreparedPublicKey(): void;
190
+ /**
191
+ * 当前槽位中的公钥(hex,按槽序)。
192
+ *
193
+ * wasm 未加载时返回空数组。
194
+ */
195
+ export declare function getPreparedPublicKeys(): string[];
196
+ /** 最后一次 `preparePublicKey` 的公钥(hex);没有则为 `null`。 */
197
+ export declare function getPreparedPublicKey(): string | null;
129
198
  /**
130
199
  * SM2 加密。
131
200
  *