@trustbaseai/account 0.1.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.
- package/README.md +351 -0
- package/dist/address.d.ts +70 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +104 -0
- package/dist/address.js.map +1 -0
- package/dist/bech32.d.ts +78 -0
- package/dist/bech32.d.ts.map +1 -0
- package/dist/bech32.js +246 -0
- package/dist/bech32.js.map +1 -0
- package/dist/bytes.d.ts +88 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/bytes.js +215 -0
- package/dist/bytes.js.map +1 -0
- package/dist/envelope.d.ts +116 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +237 -0
- package/dist/envelope.js.map +1 -0
- package/dist/errors.d.ts +48 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +41 -0
- package/dist/errors.js.map +1 -0
- package/dist/hd.d.ts +85 -0
- package/dist/hd.d.ts.map +1 -0
- package/dist/hd.js +112 -0
- package/dist/hd.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +199 -0
- package/dist/index.js.map +1 -0
- package/dist/kdf.d.ts +163 -0
- package/dist/kdf.d.ts.map +1 -0
- package/dist/kdf.js +351 -0
- package/dist/kdf.js.map +1 -0
- package/dist/keys.d.ts +55 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +114 -0
- package/dist/keys.js.map +1 -0
- package/dist/keystore.d.ts +168 -0
- package/dist/keystore.d.ts.map +1 -0
- package/dist/keystore.js +340 -0
- package/dist/keystore.js.map +1 -0
- package/dist/mnemonic.d.ts +84 -0
- package/dist/mnemonic.d.ts.map +1 -0
- package/dist/mnemonic.js +199 -0
- package/dist/mnemonic.js.map +1 -0
- package/dist/password.d.ts +79 -0
- package/dist/password.d.ts.map +1 -0
- package/dist/password.js +237 -0
- package/dist/password.js.map +1 -0
- package/dist/prf-webauthn.d.ts +85 -0
- package/dist/prf-webauthn.d.ts.map +1 -0
- package/dist/prf-webauthn.js +204 -0
- package/dist/prf-webauthn.js.map +1 -0
- package/dist/prf.d.ts +157 -0
- package/dist/prf.d.ts.map +1 -0
- package/dist/prf.js +318 -0
- package/dist/prf.js.map +1 -0
- package/dist/webcrypto.d.ts +93 -0
- package/dist/webcrypto.d.ts.map +1 -0
- package/dist/webcrypto.js +157 -0
- package/dist/webcrypto.js.map +1 -0
- package/package.json +51 -0
package/dist/kdf.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 口令 → 密钥(KDF 层):Argon2id 优先,PBKDF2-HMAC-SHA256 兜底。
|
|
4
|
+
*
|
|
5
|
+
* ## 这一层的三条硬规则
|
|
6
|
+
*
|
|
7
|
+
* 1. **参数随密文落盘,解锁时只认密文里的参数**。同一个口令 + 同一份盐 +
|
|
8
|
+
* 不同参数 = 不同的密钥。所以"解锁时按当前默认参数重算"是致命 bug:
|
|
9
|
+
* 它会解不开旧密文,而报错看起来像"密码错了"。参数里每个字段都必须参与
|
|
10
|
+
* 认证(见 keystore 的 AAD)。
|
|
11
|
+
* 2. **不在解锁时偷偷换 KDF**。若密文声明 `argon2id` 而当前环境没有 WASM,
|
|
12
|
+
* 正确行为是**报错**(`UNSUPPORTED_KDF`),不是"用 PBKDF2 试试" ——
|
|
13
|
+
* 后者会派生出另一个密钥,把"环境不支持"伪装成"口令错"。
|
|
14
|
+
* (SDK 方案 §3.3.1:PBKDF2 是**创建时**的退化方案,不是解锁时的备胎。)
|
|
15
|
+
* 3. **不可信参数必须先过界再算**。密文可能来自任何地方,`memory: 2^30` 的
|
|
16
|
+
* blob 就是一次 DoS。所以先 `assert*Params` 再派生。
|
|
17
|
+
*
|
|
18
|
+
* ## 参数默认值的依据
|
|
19
|
+
*
|
|
20
|
+
* | 项 | 默认 | 依据 |
|
|
21
|
+
* |---|---|---|
|
|
22
|
+
* | Argon2id memory / cost / parallelism | 19456 KiB / 2 / 1 | OWASP Password Storage Cheat Sheet 的 Argon2id 推荐下限(19 MiB, t=2, p=1)。本机实测 ~40ms(WASM,见包 README) |
|
|
23
|
+
* | Argon2id 版本 | 19(`0x13`) | hash-wasm 内置 Argon2 v1.3(其 README 明确写 "Argon2d, Argon2i, Argon2id (v1.3)");别的版本号一律拒,不猜 |
|
|
24
|
+
* | PBKDF2 iterations | 600000 | SDK 方案 §3.3.1 规定的下限(仅无 WASM 时使用);本实现不允许低于它**创建** |
|
|
25
|
+
* | 盐 | 16 字节随机 | 每次创建新随机,绝不复用(复用 = 同口令同密钥) |
|
|
26
|
+
* | 派生密钥长度 | 32 字节 | AES-256-GCM 的密钥长度;**格式 v1 固定 32**,将来要加长就换版本号 |
|
|
27
|
+
*
|
|
28
|
+
* ## 口令的归一化(跨平台一致性)
|
|
29
|
+
*
|
|
30
|
+
* 口令先做 **NFC** 归一化再转 UTF-8。理由不是"安全"而是"能打开":iOS/Android/
|
|
31
|
+
* 桌面的输入法对 `é` 这类字符给的是"预组合"还是"组合"序列并不一致,不做归一
|
|
32
|
+
* 会让用户**明明输对了却打不开**(而且我们会说"口令错误")。用 NFC(不是 NFKC)
|
|
33
|
+
* 是有意的:NFC 只做**规范等价**合并,不改变字面;NFKC 会把全角变半角、
|
|
34
|
+
* `①②` 变 `12`,那是真的改口令。将来若有别的语言重写本包,这一步必须一致,
|
|
35
|
+
* 否则跨端互通会碎。
|
|
36
|
+
*/
|
|
37
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
38
|
+
if (k2 === undefined) k2 = k;
|
|
39
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
40
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
41
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
42
|
+
}
|
|
43
|
+
Object.defineProperty(o, k2, desc);
|
|
44
|
+
}) : (function(o, m, k, k2) {
|
|
45
|
+
if (k2 === undefined) k2 = k;
|
|
46
|
+
o[k2] = m[k];
|
|
47
|
+
}));
|
|
48
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
49
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
50
|
+
}) : function(o, v) {
|
|
51
|
+
o["default"] = v;
|
|
52
|
+
});
|
|
53
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
54
|
+
var ownKeys = function(o) {
|
|
55
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
56
|
+
var ar = [];
|
|
57
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
58
|
+
return ar;
|
|
59
|
+
};
|
|
60
|
+
return ownKeys(o);
|
|
61
|
+
};
|
|
62
|
+
return function (mod) {
|
|
63
|
+
if (mod && mod.__esModule) return mod;
|
|
64
|
+
var result = {};
|
|
65
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
66
|
+
__setModuleDefault(result, mod);
|
|
67
|
+
return result;
|
|
68
|
+
};
|
|
69
|
+
})();
|
|
70
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
71
|
+
exports.KDF_ALGORITHMS = exports.UNTRUSTED_PBKDF2_LIMITS = exports.UNTRUSTED_ARGON2ID_LIMITS = exports.MAX_PASSWORD_LENGTH = exports.PBKDF2_MIN_CREATE_ITERATIONS = exports.DEFAULT_PBKDF2_ITERATIONS = exports.ARGON2ID_MIN_CREATE_PARAMS = exports.DEFAULT_ARGON2ID_PARAMS = exports.ARGON2ID_VERSION = exports.KDF_SALT_LENGTH = exports.DERIVED_KEY_LENGTH = void 0;
|
|
72
|
+
exports.isKdfAlgorithm = isKdfAlgorithm;
|
|
73
|
+
exports.passwordToKdfInput = passwordToKdfInput;
|
|
74
|
+
exports.assertArgon2idParams = assertArgon2idParams;
|
|
75
|
+
exports.assertPbkdf2Params = assertPbkdf2Params;
|
|
76
|
+
exports.assertCreateTimeKdfParams = assertCreateTimeKdfParams;
|
|
77
|
+
exports.isArgon2idAvailable = isArgon2idAvailable;
|
|
78
|
+
exports.resetArgon2AvailabilityCache = resetArgon2AvailabilityCache;
|
|
79
|
+
exports.deriveArgon2idKey = deriveArgon2idKey;
|
|
80
|
+
exports.derivePbkdf2Key = derivePbkdf2Key;
|
|
81
|
+
exports.generateSalt = generateSalt;
|
|
82
|
+
const pbkdf2_1 = require("@noble/hashes/pbkdf2");
|
|
83
|
+
const sha256_1 = require("@noble/hashes/sha256");
|
|
84
|
+
const bytes_1 = require("./bytes");
|
|
85
|
+
const errors_1 = require("./errors");
|
|
86
|
+
const webcrypto_1 = require("./webcrypto");
|
|
87
|
+
/** 本实现产出的派生密钥长度(AES-256 用);格式 v1 固定这个值 */
|
|
88
|
+
exports.DERIVED_KEY_LENGTH = 32;
|
|
89
|
+
/** 盐长度(字节) */
|
|
90
|
+
exports.KDF_SALT_LENGTH = 16;
|
|
91
|
+
/** hash-wasm 内置的 Argon2 版本(v1.3 = 19);其它版本号不认 */
|
|
92
|
+
exports.ARGON2ID_VERSION = 19;
|
|
93
|
+
/** Argon2id 默认参数(OWASP 下限,见文件头) */
|
|
94
|
+
exports.DEFAULT_ARGON2ID_PARAMS = {
|
|
95
|
+
memory: 19_456,
|
|
96
|
+
cost: 2,
|
|
97
|
+
parallelism: 1,
|
|
98
|
+
hashLength: exports.DERIVED_KEY_LENGTH,
|
|
99
|
+
version: exports.ARGON2ID_VERSION,
|
|
100
|
+
};
|
|
101
|
+
/** Argon2id **创建**时的参数下限(低于它 = 不符合设计,直接拒) */
|
|
102
|
+
exports.ARGON2ID_MIN_CREATE_PARAMS = {
|
|
103
|
+
memory: 19_456,
|
|
104
|
+
cost: 2,
|
|
105
|
+
};
|
|
106
|
+
/** PBKDF2 默认迭代次数 */
|
|
107
|
+
exports.DEFAULT_PBKDF2_ITERATIONS = 600_000;
|
|
108
|
+
/** PBKDF2 **创建**时的迭代次数下限(SDK 方案 §3.3.1「≥60 万次」) */
|
|
109
|
+
exports.PBKDF2_MIN_CREATE_ITERATIONS = 600_000;
|
|
110
|
+
/** 口令长度上限(SDK 方案 §3.3.6「上限制长度(≤128)防 DoS」) */
|
|
111
|
+
exports.MAX_PASSWORD_LENGTH = 128;
|
|
112
|
+
/**
|
|
113
|
+
* 不可信 KDF 参数的取值边界(**解码外来密文**时防 DoS 用)。
|
|
114
|
+
* 上限取"远高于任何合理参数、又低到打不出资源灾难"的位置。
|
|
115
|
+
*/
|
|
116
|
+
exports.UNTRUSTED_ARGON2ID_LIMITS = {
|
|
117
|
+
memory: { min: 8, max: 1_048_576 },
|
|
118
|
+
cost: { min: 1, max: 1000 },
|
|
119
|
+
parallelism: { min: 1, max: 64 },
|
|
120
|
+
};
|
|
121
|
+
exports.UNTRUSTED_PBKDF2_LIMITS = {
|
|
122
|
+
iterations: { min: 1, max: 10_000_000 },
|
|
123
|
+
};
|
|
124
|
+
exports.KDF_ALGORITHMS = ['argon2id', 'pbkdf2'];
|
|
125
|
+
function isKdfAlgorithm(value) {
|
|
126
|
+
return value === 'argon2id' || value === 'pbkdf2';
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 口令 → KDF 输入字节(NFC 归一 + UTF-8 + 长度上限)。
|
|
130
|
+
*
|
|
131
|
+
* 返回的是**我们自己的缓冲区**,用完可以 `wipe()`;口令字符串本身清不掉
|
|
132
|
+
* (见 bytes.ts 的 `wipe` 说明),所以这一层的价值是"把不可清的东西缩小到
|
|
133
|
+
* 一次函数调用的范围"。
|
|
134
|
+
*/
|
|
135
|
+
function passwordToKdfInput(password) {
|
|
136
|
+
if (typeof password !== 'string') {
|
|
137
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', '口令必须是字符串');
|
|
138
|
+
}
|
|
139
|
+
if (password.length === 0) {
|
|
140
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', '口令不能为空');
|
|
141
|
+
}
|
|
142
|
+
if (password.length > exports.MAX_PASSWORD_LENGTH) {
|
|
143
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', `口令长度不得超过 ${exports.MAX_PASSWORD_LENGTH} 个字符(这是防 DoS 的上限,不是强度要求)`);
|
|
144
|
+
}
|
|
145
|
+
return (0, bytes_1.utf8ToBytes)(password.normalize('NFC'));
|
|
146
|
+
}
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// 参数校验
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
function requireIntegerInRange(value, min, max, field) {
|
|
151
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < min || value > max) {
|
|
152
|
+
throw new errors_1.AccountError('INVALID_BLOB', `kdfParams.${field} 非法(需 ${min}..${max} 的整数)`);
|
|
153
|
+
}
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
function requireBase64OfLength(value, length, field) {
|
|
157
|
+
if (typeof value !== 'string') {
|
|
158
|
+
throw new errors_1.AccountError('INVALID_BLOB', `${field} 必须是 base64 字符串`);
|
|
159
|
+
}
|
|
160
|
+
let decoded;
|
|
161
|
+
try {
|
|
162
|
+
decoded = (0, bytes_1.base64Decode)(value);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
throw new errors_1.AccountError('INVALID_BLOB', `${field} 不是合法的 base64`);
|
|
166
|
+
}
|
|
167
|
+
if (decoded.length !== length) {
|
|
168
|
+
throw new errors_1.AccountError('INVALID_BLOB', `${field} 解码后必须是 ${length} 字节(实际 ${decoded.length})`);
|
|
169
|
+
}
|
|
170
|
+
return decoded;
|
|
171
|
+
}
|
|
172
|
+
/** 校验(不可信的)Argon2id 参数;不合法抛 `INVALID_BLOB` / `UNSUPPORTED_KDF` */
|
|
173
|
+
function assertArgon2idParams(value) {
|
|
174
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
175
|
+
throw new errors_1.AccountError('INVALID_BLOB', 'kdfParams 必须是对象');
|
|
176
|
+
}
|
|
177
|
+
const params = value;
|
|
178
|
+
requireBase64OfLength(params.salt, exports.KDF_SALT_LENGTH, 'kdfParams.salt');
|
|
179
|
+
requireIntegerInRange(params.memory, exports.UNTRUSTED_ARGON2ID_LIMITS.memory.min, exports.UNTRUSTED_ARGON2ID_LIMITS.memory.max, 'memory');
|
|
180
|
+
requireIntegerInRange(params.cost, exports.UNTRUSTED_ARGON2ID_LIMITS.cost.min, exports.UNTRUSTED_ARGON2ID_LIMITS.cost.max, 'cost');
|
|
181
|
+
const parallelism = requireIntegerInRange(params.parallelism, exports.UNTRUSTED_ARGON2ID_LIMITS.parallelism.min, exports.UNTRUSTED_ARGON2ID_LIMITS.parallelism.max, 'parallelism');
|
|
182
|
+
if (params.memory < 8 * parallelism) {
|
|
183
|
+
throw new errors_1.AccountError('INVALID_BLOB', 'kdfParams.memory 必须 ≥ 8 × parallelism(Argon2 规范要求)');
|
|
184
|
+
}
|
|
185
|
+
if (params.hashLength !== exports.DERIVED_KEY_LENGTH) {
|
|
186
|
+
throw new errors_1.AccountError('INVALID_BLOB', `密钥库格式 v1 的派生密钥长度固定为 ${exports.DERIVED_KEY_LENGTH} 字节(本密文声明 ${String(params.hashLength)})`);
|
|
187
|
+
}
|
|
188
|
+
if (params.version !== exports.ARGON2ID_VERSION) {
|
|
189
|
+
throw new errors_1.AccountError('UNSUPPORTED_KDF', `只支持 Argon2 版本 ${exports.ARGON2ID_VERSION}(v1.3);本密文声明的是 ${String(params.version)}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** 校验(不可信的)PBKDF2 参数;不合法抛 `INVALID_BLOB` / `UNSUPPORTED_KDF` */
|
|
193
|
+
function assertPbkdf2Params(value) {
|
|
194
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
195
|
+
throw new errors_1.AccountError('INVALID_BLOB', 'kdfParams 必须是对象');
|
|
196
|
+
}
|
|
197
|
+
const params = value;
|
|
198
|
+
requireBase64OfLength(params.salt, exports.KDF_SALT_LENGTH, 'kdfParams.salt');
|
|
199
|
+
requireIntegerInRange(params.iterations, exports.UNTRUSTED_PBKDF2_LIMITS.iterations.min, exports.UNTRUSTED_PBKDF2_LIMITS.iterations.max, 'iterations');
|
|
200
|
+
if (params.hashLength !== exports.DERIVED_KEY_LENGTH) {
|
|
201
|
+
throw new errors_1.AccountError('INVALID_BLOB', `密钥库格式 v1 的派生密钥长度固定为 ${exports.DERIVED_KEY_LENGTH} 字节(本密文声明 ${String(params.hashLength)})`);
|
|
202
|
+
}
|
|
203
|
+
if (params.hash !== 'sha256') {
|
|
204
|
+
throw new errors_1.AccountError('UNSUPPORTED_KDF', `只支持 PBKDF2-HMAC-SHA256;本密文声明的是 ${String(params.hash)}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* **创建**时的参数下限校验(调用方给的参数是我们自己的口径,不合格就拒)。
|
|
209
|
+
*
|
|
210
|
+
* 与解锁路径分开:解锁要"照 blob 走",不能因为参数低于今天的下限就拒
|
|
211
|
+
* —— 那是别人(或旧版本的我们)的密文,拒了等于把人锁在门外。
|
|
212
|
+
*/
|
|
213
|
+
function assertCreateTimeKdfParams(algorithm, params) {
|
|
214
|
+
if (algorithm === 'argon2id') {
|
|
215
|
+
const argon = params;
|
|
216
|
+
assertArgon2idParams(argon);
|
|
217
|
+
if (argon.memory < exports.ARGON2ID_MIN_CREATE_PARAMS.memory || argon.cost < exports.ARGON2ID_MIN_CREATE_PARAMS.cost) {
|
|
218
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', `Argon2id 参数低于下限:memory ≥ ${exports.ARGON2ID_MIN_CREATE_PARAMS.memory} KiB 且 cost ≥ ${exports.ARGON2ID_MIN_CREATE_PARAMS.cost}` +
|
|
219
|
+
`(收到 memory=${argon.memory}, cost=${argon.cost})`);
|
|
220
|
+
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const pbkdf2Params = params;
|
|
224
|
+
assertPbkdf2Params(pbkdf2Params);
|
|
225
|
+
if (pbkdf2Params.iterations < exports.PBKDF2_MIN_CREATE_ITERATIONS) {
|
|
226
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', `PBKDF2 迭代次数不得低于 ${exports.PBKDF2_MIN_CREATE_ITERATIONS}(收到 ${pbkdf2Params.iterations})`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
let argon2Probe = null;
|
|
230
|
+
/**
|
|
231
|
+
* 真跑一次的探测(不是只查 `typeof WebAssembly`)。
|
|
232
|
+
*
|
|
233
|
+
* 为什么要真跑:CSP 里没有 `wasm-unsafe-eval` 时 `WebAssembly.instantiate`
|
|
234
|
+
* 会抛错,而 `typeof WebAssembly === 'object'` 照样为 true —— 只查存在性
|
|
235
|
+
* 会朝"能创建 keystore、将来打不开"的方向失败。这里用一个 8 KiB / 1 迭代的
|
|
236
|
+
* 极小参数实跑一次,失败即判定不可用(于是创建时退到 PBKDF2)。
|
|
237
|
+
*
|
|
238
|
+
* 结果被缓存(包括失败结果):探测本身约几毫秒,但没必要每次创建都付。
|
|
239
|
+
*/
|
|
240
|
+
function isArgon2idAvailable() {
|
|
241
|
+
if (argon2Probe === null) {
|
|
242
|
+
argon2Probe = probeArgon2id();
|
|
243
|
+
}
|
|
244
|
+
return argon2Probe;
|
|
245
|
+
}
|
|
246
|
+
/** 仅供测试:清掉探测缓存(例如模拟"这台设备没有 WASM") */
|
|
247
|
+
function resetArgon2AvailabilityCache() {
|
|
248
|
+
argon2Probe = null;
|
|
249
|
+
}
|
|
250
|
+
async function probeArgon2id() {
|
|
251
|
+
// 手写这一处最小声明而不是开 DOM lib:`WebAssembly` 的类型在 ES2022 lib 里没有
|
|
252
|
+
// (它在 DOM/WebWorker lib 里),而本包刻意不开 DOM lib(见 webcrypto.ts 文件头)
|
|
253
|
+
const wasm = globalThis.WebAssembly;
|
|
254
|
+
if (!wasm || typeof wasm.instantiate !== 'function')
|
|
255
|
+
return false;
|
|
256
|
+
try {
|
|
257
|
+
const argon2id = await loadArgon2id();
|
|
258
|
+
if (argon2id === null)
|
|
259
|
+
return false;
|
|
260
|
+
const sample = await argon2id({
|
|
261
|
+
password: (0, bytes_1.utf8ToBytes)('probe'),
|
|
262
|
+
salt: new Uint8Array(8),
|
|
263
|
+
iterations: 1,
|
|
264
|
+
parallelism: 1,
|
|
265
|
+
memorySize: 8,
|
|
266
|
+
hashLength: 8,
|
|
267
|
+
outputType: 'binary',
|
|
268
|
+
});
|
|
269
|
+
return sample.length === 8;
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 动态载入 hash-wasm。
|
|
277
|
+
*
|
|
278
|
+
* 用动态 `import()` 而不是顶层 import:顶层 import 会让"没有 WASM 的环境"在
|
|
279
|
+
* **模块加载阶段**就炸掉整个包,而 PBKDF2 回退路径本该在那样的环境里正常工作。
|
|
280
|
+
* 动态引入把失败限制在真正需要 Argon2id 的那一刻。
|
|
281
|
+
* (打包器可能把它放同一 chunk、只是延迟执行;这是可接受的代价。)
|
|
282
|
+
*/
|
|
283
|
+
async function loadArgon2id() {
|
|
284
|
+
try {
|
|
285
|
+
const mod = await Promise.resolve().then(() => __importStar(require('hash-wasm')));
|
|
286
|
+
const fn = mod.argon2id;
|
|
287
|
+
return typeof fn === 'function' ? fn : null;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// 派生
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
/**
|
|
297
|
+
* Argon2id 派生。本环境跑不起来(没有 WASM / 被 CSP 拦下 / 模块加载失败)时
|
|
298
|
+
* 抛 `UNSUPPORTED_KDF` —— **不回退**,理由见文件头第 2 条。
|
|
299
|
+
*
|
|
300
|
+
* 注意这里把底层调用**整个**包在 try/catch 里:hash-wasm 的模块能加载、
|
|
301
|
+
* 但 WASM 实例化失败的情况(CSP 缺 `wasm-unsafe-eval`)是在**调用时**才炸的,
|
|
302
|
+
* 如果只检查"模块加载成功"就会把浏览器原生的错误直接抛给上层,
|
|
303
|
+
* 而调用方需要的是"这个环境跑不了 Argon2id"这个可分支的语义。
|
|
304
|
+
*/
|
|
305
|
+
async function deriveArgon2idKey(params, kdfInput) {
|
|
306
|
+
assertArgon2idParams(params);
|
|
307
|
+
const argon2id = await loadArgon2id();
|
|
308
|
+
if (argon2id === null) {
|
|
309
|
+
throw new errors_1.AccountError('UNSUPPORTED_KDF', '本环境无法运行 Argon2id(WebAssembly 不可用或被 CSP 拦下),无法解锁这份密钥库。' +
|
|
310
|
+
'请换一个支持 WebAssembly 的环境打开,或用助记词(纸密钥)恢复。');
|
|
311
|
+
}
|
|
312
|
+
let derived;
|
|
313
|
+
try {
|
|
314
|
+
derived = await argon2id({
|
|
315
|
+
password: kdfInput,
|
|
316
|
+
salt: (0, bytes_1.base64Decode)(params.salt),
|
|
317
|
+
iterations: params.cost,
|
|
318
|
+
parallelism: params.parallelism,
|
|
319
|
+
memorySize: params.memory,
|
|
320
|
+
hashLength: params.hashLength,
|
|
321
|
+
outputType: 'binary',
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
throw new errors_1.AccountError('UNSUPPORTED_KDF', '本环境无法运行 Argon2id(WebAssembly 实例化失败 —— 常见原因是 CSP 里缺 `wasm-unsafe-eval`)。' +
|
|
326
|
+
'请换一个环境打开,或用助记词(纸密钥)恢复。', { reason: error instanceof Error ? error.message : String(error) });
|
|
327
|
+
}
|
|
328
|
+
if (!(derived instanceof Uint8Array) || derived.length !== params.hashLength) {
|
|
329
|
+
throw new errors_1.AccountError('UNSUPPORTED_KDF', 'hash-wasm 返回的 Argon2id 输出长度不符');
|
|
330
|
+
}
|
|
331
|
+
return derived;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* PBKDF2-HMAC-SHA256 派生(无 WASM 时的兜底路径)。
|
|
335
|
+
*
|
|
336
|
+
* 用 `@noble/hashes/pbkdf2` 而不是 WebCrypto 的 `deriveBits`:一是确定性更强
|
|
337
|
+
* (同构环境给同样的字节,便于与 OpenSSL 对照复算),二是这条路径的定位就是
|
|
338
|
+
* "平台能力缺失时的备胎",不该再依赖平台。
|
|
339
|
+
*/
|
|
340
|
+
async function derivePbkdf2Key(params, kdfInput) {
|
|
341
|
+
assertPbkdf2Params(params);
|
|
342
|
+
return (0, pbkdf2_1.pbkdf2)(sha256_1.sha256, kdfInput, (0, bytes_1.base64Decode)(params.salt), {
|
|
343
|
+
c: params.iterations,
|
|
344
|
+
dkLen: params.hashLength,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
/** 生成一个新盐(base64)—— 只用于**创建**路径 */
|
|
348
|
+
function generateSalt() {
|
|
349
|
+
return (0, bytes_1.base64Encode)((0, webcrypto_1.randomBytes)(exports.KDF_SALT_LENGTH));
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=kdf.js.map
|
package/dist/kdf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kdf.js","sourceRoot":"","sources":["../src/kdf.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6EH,wCAEC;AASD,gDAcC;AA8BD,oDAkCC;AAGD,gDAqBC;AAQD,8DAqBC;AA4BD,kDAKC;AAGD,oEAEC;AAwDD,8CAiCC;AASD,0CAMC;AAGD,oCAEC;AA5WD,iDAA8C;AAC9C,iDAA8C;AAE9C,mCAAkE;AAClE,qCAAwC;AACxC,2CAA0C;AAE1C,0CAA0C;AAC7B,QAAA,kBAAkB,GAAG,EAAE,CAAC;AACrC,cAAc;AACD,QAAA,eAAe,GAAG,EAAE,CAAC;AAClC,iDAAiD;AACpC,QAAA,gBAAgB,GAAG,EAAE,CAAC;AACnC,mCAAmC;AACtB,QAAA,uBAAuB,GAAG;IACrC,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,CAAC;IACP,WAAW,EAAE,CAAC;IACd,UAAU,EAAE,0BAAkB;IAC9B,OAAO,EAAE,wBAAgB;CACjB,CAAC;AACX,6CAA6C;AAChC,QAAA,0BAA0B,GAAG;IACxC,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,CAAC;CACC,CAAC;AACX,oBAAoB;AACP,QAAA,yBAAyB,GAAG,OAAO,CAAC;AACjD,mDAAmD;AACtC,QAAA,4BAA4B,GAAG,OAAO,CAAC;AACpD,8CAA8C;AACjC,QAAA,mBAAmB,GAAG,GAAG,CAAC;AAEvC;;;GAGG;AACU,QAAA,yBAAyB,GAAG;IACvC,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE;IAClC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE;IAC3B,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;CACxB,CAAC;AACE,QAAA,uBAAuB,GAAG;IACrC,UAAU,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE;CAC/B,CAAC;AA6BE,QAAA,cAAc,GAA4B,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AAE9E,SAAgB,cAAc,CAAC,KAAc;IAC3C,OAAO,KAAK,KAAK,UAAU,IAAI,KAAK,KAAK,QAAQ,CAAC;AACpD,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAC,QAAgB;IACjD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,MAAM,IAAI,qBAAY,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,qBAAY,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,2BAAmB,EAAE,CAAC;QAC1C,MAAM,IAAI,qBAAY,CACpB,kBAAkB,EAClB,YAAY,2BAAmB,0BAA0B,CAC1D,CAAC;IACJ,CAAC;IACD,OAAO,IAAA,mBAAW,EAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,8EAA8E;AAC9E,OAAO;AACP,8EAA8E;AAE9E,SAAS,qBAAqB,CAAC,KAAc,EAAE,GAAW,EAAE,GAAW,EAAE,KAAa;IACpF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,GAAG,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QACxF,MAAM,IAAI,qBAAY,CAAC,cAAc,EAAE,aAAa,KAAK,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAc,EAAE,MAAc,EAAE,KAAa;IAC1E,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,qBAAY,CAAC,cAAc,EAAE,GAAG,KAAK,iBAAiB,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,OAAmB,CAAC;IACxB,IAAI,CAAC;QACH,OAAO,GAAG,IAAA,oBAAY,EAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,qBAAY,CAAC,cAAc,EAAE,GAAG,KAAK,eAAe,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC9B,MAAM,IAAI,qBAAY,CAAC,cAAc,EAAE,GAAG,KAAK,WAAW,MAAM,UAAU,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,kEAAkE;AAClE,SAAgB,oBAAoB,CAAC,KAAc;IACjD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,qBAAY,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,qBAAqB,CAAC,MAAM,CAAC,IAAI,EAAE,uBAAe,EAAE,gBAAgB,CAAC,CAAC;IACtE,qBAAqB,CACnB,MAAM,CAAC,MAAM,EACb,iCAAyB,CAAC,MAAM,CAAC,GAAG,EACpC,iCAAyB,CAAC,MAAM,CAAC,GAAG,EACpC,QAAQ,CACT,CAAC;IACF,qBAAqB,CAAC,MAAM,CAAC,IAAI,EAAE,iCAAyB,CAAC,IAAI,CAAC,GAAG,EAAE,iCAAyB,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACnH,MAAM,WAAW,GAAG,qBAAqB,CACvC,MAAM,CAAC,WAAW,EAClB,iCAAyB,CAAC,WAAW,CAAC,GAAG,EACzC,iCAAyB,CAAC,WAAW,CAAC,GAAG,EACzC,aAAa,CACd,CAAC;IACF,IAAK,MAAM,CAAC,MAAiB,GAAG,CAAC,GAAG,WAAW,EAAE,CAAC;QAChD,MAAM,IAAI,qBAAY,CAAC,cAAc,EAAE,oDAAoD,CAAC,CAAC;IAC/F,CAAC;IACD,IAAI,MAAM,CAAC,UAAU,KAAK,0BAAkB,EAAE,CAAC;QAC7C,MAAM,IAAI,qBAAY,CACpB,cAAc,EACd,uBAAuB,0BAAkB,aAAa,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CACnF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,KAAK,wBAAgB,EAAE,CAAC;QACxC,MAAM,IAAI,qBAAY,CACpB,iBAAiB,EACjB,iBAAiB,wBAAgB,kBAAkB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAC5E,CAAC;IACJ,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,SAAgB,kBAAkB,CAAC,KAAc;IAC/C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,qBAAY,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,qBAAqB,CAAC,MAAM,CAAC,IAAI,EAAE,uBAAe,EAAE,gBAAgB,CAAC,CAAC;IACtE,qBAAqB,CACnB,MAAM,CAAC,UAAU,EACjB,+BAAuB,CAAC,UAAU,CAAC,GAAG,EACtC,+BAAuB,CAAC,UAAU,CAAC,GAAG,EACtC,YAAY,CACb,CAAC;IACF,IAAI,MAAM,CAAC,UAAU,KAAK,0BAAkB,EAAE,CAAC;QAC7C,MAAM,IAAI,qBAAY,CACpB,cAAc,EACd,uBAAuB,0BAAkB,aAAa,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CACnF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,IAAI,qBAAY,CAAC,iBAAiB,EAAE,kCAAkC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrG,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAgB,yBAAyB,CAAC,SAAuB,EAAE,MAAqC;IACtG,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,MAAwB,CAAC;QACvC,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,KAAK,CAAC,MAAM,GAAG,kCAA0B,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,GAAG,kCAA0B,CAAC,IAAI,EAAE,CAAC;YACrG,MAAM,IAAI,qBAAY,CACpB,kBAAkB,EAClB,4BAA4B,kCAA0B,CAAC,MAAM,iBAAiB,kCAA0B,CAAC,IAAI,EAAE;gBAC7G,cAAc,KAAK,CAAC,MAAM,UAAU,KAAK,CAAC,IAAI,GAAG,CACpD,CAAC;QACJ,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,YAAY,GAAG,MAAsB,CAAC;IAC5C,kBAAkB,CAAC,YAAY,CAAC,CAAC;IACjC,IAAI,YAAY,CAAC,UAAU,GAAG,oCAA4B,EAAE,CAAC;QAC3D,MAAM,IAAI,qBAAY,CACpB,kBAAkB,EAClB,mBAAmB,oCAA4B,OAAO,YAAY,CAAC,UAAU,GAAG,CACjF,CAAC;IACJ,CAAC;AACH,CAAC;AAgBD,IAAI,WAAW,GAA4B,IAAI,CAAC;AAEhD;;;;;;;;;GASG;AACH,SAAgB,mBAAmB;IACjC,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,WAAW,GAAG,aAAa,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,qCAAqC;AACrC,SAAgB,4BAA4B;IAC1C,WAAW,GAAG,IAAI,CAAC;AACrB,CAAC;AAED,KAAK,UAAU,aAAa;IAC1B,0DAA0D;IAC1D,+DAA+D;IAC/D,MAAM,IAAI,GAAI,UAA0D,CAAC,WAAW,CAAC;IACrF,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,WAAW,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IAClE,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;QACtC,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QACpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC;YAC5B,QAAQ,EAAE,IAAA,mBAAW,EAAC,OAAO,CAAC;YAC9B,IAAI,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC;YACvB,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,CAAC;YACd,UAAU,EAAE,CAAC;YACb,UAAU,EAAE,CAAC;YACb,UAAU,EAAE,QAAQ;SACrB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,YAAY;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAY,wDAAa,WAAW,GAAC,CAAC;QAC/C,MAAM,EAAE,GAAI,GAA8B,CAAC,QAAQ,CAAC;QACpD,OAAO,OAAO,EAAE,KAAK,UAAU,CAAC,CAAC,CAAE,EAAiB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,KAAK;AACL,8EAA8E;AAE9E;;;;;;;;GAQG;AACI,KAAK,UAAU,iBAAiB,CAAC,MAAsB,EAAE,QAAoB;IAClF,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,IAAI,qBAAY,CACpB,iBAAiB,EACjB,uDAAuD;YACrD,wCAAwC,CAC3C,CAAC;IACJ,CAAC;IACD,IAAI,OAAmB,CAAC;IACxB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC;YACvB,QAAQ,EAAE,QAAQ;YAClB,IAAI,EAAE,IAAA,oBAAY,EAAC,MAAM,CAAC,IAAI,CAAC;YAC/B,UAAU,EAAE,MAAM,CAAC,IAAI;YACvB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,UAAU,EAAE,MAAM,CAAC,MAAM;YACzB,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,UAAU,EAAE,QAAQ;SACrB,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,qBAAY,CACpB,iBAAiB,EACjB,yEAAyE;YACvE,wBAAwB,EAC1B,EAAE,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACnE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,OAAO,YAAY,UAAU,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,UAAU,EAAE,CAAC;QAC7E,MAAM,IAAI,qBAAY,CAAC,iBAAiB,EAAE,+BAA+B,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,eAAe,CAAC,MAAoB,EAAE,QAAoB;IAC9E,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,OAAO,IAAA,eAAM,EAAC,eAAM,EAAE,QAAQ,EAAE,IAAA,oBAAY,EAAC,MAAM,CAAC,IAAI,CAAC,EAAE;QACzD,CAAC,EAAE,MAAM,CAAC,UAAU;QACpB,KAAK,EAAE,MAAM,CAAC,UAAU;KACzB,CAAC,CAAC;AACL,CAAC;AAED,mCAAmC;AACnC,SAAgB,YAAY;IAC1B,OAAO,IAAA,oBAAY,EAAC,IAAA,uBAAW,EAAC,uBAAe,CAAC,CAAC,CAAC;AACpD,CAAC"}
|
package/dist/keys.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* secp256k1 密钥工具。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么只做"压缩公钥"这一件事
|
|
5
|
+
*
|
|
6
|
+
* 链上账户地址的输入是 **33 字节压缩公钥**(入口文档 §8.3.2 ②)。密钥生成与
|
|
7
|
+
* 点运算交给 `@noble/curves`(同族库,浏览器/Node 同构、audited、零依赖膨胀),
|
|
8
|
+
* 本文件只负责:长度/前缀校验、压缩形态归一、随机私钥的取值合法性。
|
|
9
|
+
*
|
|
10
|
+
* ## 随机源
|
|
11
|
+
*
|
|
12
|
+
* 用本包自己的 `randomBytes`(只有 `crypto.getRandomValues` 一条路,拿不到就
|
|
13
|
+
* `CRYPTO_UNAVAILABLE`),不用 noble 自带的 `randomPrivateKey`:**错误语义要统一
|
|
14
|
+
* 在本包一层**,而且"随机源不可用"必须是一个显式失败,不能悄悄降级。
|
|
15
|
+
*
|
|
16
|
+
* 私钥的合法区间是 `[1, n-1]`(n 是 secp256k1 的阶)。随机 32 字节落在外面的
|
|
17
|
+
* 概率约 2^-128 —— 但它是**确定性的边界条件**,所以按"重试几次、都失败就报错"
|
|
18
|
+
* 处理,不做静默接受。
|
|
19
|
+
*/
|
|
20
|
+
/** 私钥长度(32 字节标量) */
|
|
21
|
+
export declare const PRIVATE_KEY_LENGTH = 32;
|
|
22
|
+
/** 压缩公钥长度(33 字节:1 字节前缀 + 32 字节 X) */
|
|
23
|
+
export declare const COMPRESSED_PUBLIC_KEY_LENGTH = 33;
|
|
24
|
+
/** 非压缩公钥长度(65 字节)—— 只做输入兼容,绝不产出 */
|
|
25
|
+
export declare const UNCOMPRESSED_PUBLIC_KEY_LENGTH = 65;
|
|
26
|
+
/**
|
|
27
|
+
* 私钥是否合法:32 字节且在 `[1, n-1]` 内。
|
|
28
|
+
* 全 0 与 ≥ n 都会被拒(这两者是真实存在的"看起来像私钥"的错值)。
|
|
29
|
+
*/
|
|
30
|
+
export declare function isValidPrivateKey(value: unknown): value is Uint8Array;
|
|
31
|
+
/**
|
|
32
|
+
* 生成一把新的私钥(32 字节)。
|
|
33
|
+
*
|
|
34
|
+
* 失败(连续 8 次都落在非法区间 —— 概率 2^-900 量级)时抛错而不是返回可疑值。
|
|
35
|
+
*/
|
|
36
|
+
export declare function generatePrivateKey(): Uint8Array;
|
|
37
|
+
/**
|
|
38
|
+
* 私钥 → 33 字节压缩公钥。
|
|
39
|
+
* 私钥非法(长度/区间)时抛 `INVALID_ARGUMENT`,不做"修复"。
|
|
40
|
+
*/
|
|
41
|
+
export declare function publicKeyFromPrivateKey(privateKey: Uint8Array): Uint8Array;
|
|
42
|
+
/**
|
|
43
|
+
* 公钥是否合法:33 字节压缩(`02`/`03` 前缀)或 65 字节非压缩(`04` 前缀),
|
|
44
|
+
* 且点确实在曲线上(子群/曲线方程都校验,不接受随便一串字节)。
|
|
45
|
+
*/
|
|
46
|
+
export declare function isValidPublicKey(value: unknown): value is Uint8Array;
|
|
47
|
+
/**
|
|
48
|
+
* 归一成 33 字节压缩公钥。
|
|
49
|
+
*
|
|
50
|
+
* 接受压缩(33B)与非压缩(65B)两种输入 —— 外部系统(SDK 之外的钱包、
|
|
51
|
+
* 链上查询结果)给的公钥两种形态都见过,但**我们内部与地址派生只用压缩形态**:
|
|
52
|
+
* 非压缩会派生出另一个地址,这是"同一个私钥有两个地址"的经典坑。
|
|
53
|
+
*/
|
|
54
|
+
export declare function compressPublicKey(publicKey: Uint8Array): Uint8Array;
|
|
55
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH,oBAAoB;AACpB,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,qCAAqC;AACrC,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAC/C,mCAAmC;AACnC,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAMjD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAMrE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,UAAU,CAM/C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAK1E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAgBpE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,UAAU,GAAG,UAAU,CAMnE"}
|
package/dist/keys.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* secp256k1 密钥工具。
|
|
4
|
+
*
|
|
5
|
+
* ## 为什么只做"压缩公钥"这一件事
|
|
6
|
+
*
|
|
7
|
+
* 链上账户地址的输入是 **33 字节压缩公钥**(入口文档 §8.3.2 ②)。密钥生成与
|
|
8
|
+
* 点运算交给 `@noble/curves`(同族库,浏览器/Node 同构、audited、零依赖膨胀),
|
|
9
|
+
* 本文件只负责:长度/前缀校验、压缩形态归一、随机私钥的取值合法性。
|
|
10
|
+
*
|
|
11
|
+
* ## 随机源
|
|
12
|
+
*
|
|
13
|
+
* 用本包自己的 `randomBytes`(只有 `crypto.getRandomValues` 一条路,拿不到就
|
|
14
|
+
* `CRYPTO_UNAVAILABLE`),不用 noble 自带的 `randomPrivateKey`:**错误语义要统一
|
|
15
|
+
* 在本包一层**,而且"随机源不可用"必须是一个显式失败,不能悄悄降级。
|
|
16
|
+
*
|
|
17
|
+
* 私钥的合法区间是 `[1, n-1]`(n 是 secp256k1 的阶)。随机 32 字节落在外面的
|
|
18
|
+
* 概率约 2^-128 —— 但它是**确定性的边界条件**,所以按"重试几次、都失败就报错"
|
|
19
|
+
* 处理,不做静默接受。
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.UNCOMPRESSED_PUBLIC_KEY_LENGTH = exports.COMPRESSED_PUBLIC_KEY_LENGTH = exports.PRIVATE_KEY_LENGTH = void 0;
|
|
23
|
+
exports.isValidPrivateKey = isValidPrivateKey;
|
|
24
|
+
exports.generatePrivateKey = generatePrivateKey;
|
|
25
|
+
exports.publicKeyFromPrivateKey = publicKeyFromPrivateKey;
|
|
26
|
+
exports.isValidPublicKey = isValidPublicKey;
|
|
27
|
+
exports.compressPublicKey = compressPublicKey;
|
|
28
|
+
const secp256k1_1 = require("@noble/curves/secp256k1");
|
|
29
|
+
const webcrypto_1 = require("./webcrypto");
|
|
30
|
+
const errors_1 = require("./errors");
|
|
31
|
+
/** 私钥长度(32 字节标量) */
|
|
32
|
+
exports.PRIVATE_KEY_LENGTH = 32;
|
|
33
|
+
/** 压缩公钥长度(33 字节:1 字节前缀 + 32 字节 X) */
|
|
34
|
+
exports.COMPRESSED_PUBLIC_KEY_LENGTH = 33;
|
|
35
|
+
/** 非压缩公钥长度(65 字节)—— 只做输入兼容,绝不产出 */
|
|
36
|
+
exports.UNCOMPRESSED_PUBLIC_KEY_LENGTH = 65;
|
|
37
|
+
const COMPRESSED_PREFIX_EVEN = 0x02;
|
|
38
|
+
const COMPRESSED_PREFIX_ODD = 0x03;
|
|
39
|
+
const UNCOMPRESSED_PREFIX = 0x04;
|
|
40
|
+
/**
|
|
41
|
+
* 私钥是否合法:32 字节且在 `[1, n-1]` 内。
|
|
42
|
+
* 全 0 与 ≥ n 都会被拒(这两者是真实存在的"看起来像私钥"的错值)。
|
|
43
|
+
*/
|
|
44
|
+
function isValidPrivateKey(value) {
|
|
45
|
+
return (value instanceof Uint8Array &&
|
|
46
|
+
value.length === exports.PRIVATE_KEY_LENGTH &&
|
|
47
|
+
secp256k1_1.secp256k1.utils.isValidSecretKey(value));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 生成一把新的私钥(32 字节)。
|
|
51
|
+
*
|
|
52
|
+
* 失败(连续 8 次都落在非法区间 —— 概率 2^-900 量级)时抛错而不是返回可疑值。
|
|
53
|
+
*/
|
|
54
|
+
function generatePrivateKey() {
|
|
55
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
56
|
+
const candidate = (0, webcrypto_1.randomBytes)(exports.PRIVATE_KEY_LENGTH);
|
|
57
|
+
if (isValidPrivateKey(candidate))
|
|
58
|
+
return candidate;
|
|
59
|
+
}
|
|
60
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', '连续 8 次生成的随机字节都不是合法私钥 —— 随机源可能异常');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 私钥 → 33 字节压缩公钥。
|
|
64
|
+
* 私钥非法(长度/区间)时抛 `INVALID_ARGUMENT`,不做"修复"。
|
|
65
|
+
*/
|
|
66
|
+
function publicKeyFromPrivateKey(privateKey) {
|
|
67
|
+
if (!isValidPrivateKey(privateKey)) {
|
|
68
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', `私钥必须是 ${exports.PRIVATE_KEY_LENGTH} 字节且在 [1, n-1] 区间内`);
|
|
69
|
+
}
|
|
70
|
+
return secp256k1_1.secp256k1.getPublicKey(privateKey, true);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 公钥是否合法:33 字节压缩(`02`/`03` 前缀)或 65 字节非压缩(`04` 前缀),
|
|
74
|
+
* 且点确实在曲线上(子群/曲线方程都校验,不接受随便一串字节)。
|
|
75
|
+
*/
|
|
76
|
+
function isValidPublicKey(value) {
|
|
77
|
+
if (!(value instanceof Uint8Array))
|
|
78
|
+
return false;
|
|
79
|
+
if (value.length === exports.COMPRESSED_PUBLIC_KEY_LENGTH) {
|
|
80
|
+
const prefix = value[0];
|
|
81
|
+
if (prefix !== COMPRESSED_PREFIX_EVEN && prefix !== COMPRESSED_PREFIX_ODD)
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
else if (value.length === exports.UNCOMPRESSED_PUBLIC_KEY_LENGTH) {
|
|
85
|
+
if (value[0] !== UNCOMPRESSED_PREFIX)
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
secp256k1_1.secp256k1.Point.fromHex(value).assertValidity();
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 归一成 33 字节压缩公钥。
|
|
101
|
+
*
|
|
102
|
+
* 接受压缩(33B)与非压缩(65B)两种输入 —— 外部系统(SDK 之外的钱包、
|
|
103
|
+
* 链上查询结果)给的公钥两种形态都见过,但**我们内部与地址派生只用压缩形态**:
|
|
104
|
+
* 非压缩会派生出另一个地址,这是"同一个私钥有两个地址"的经典坑。
|
|
105
|
+
*/
|
|
106
|
+
function compressPublicKey(publicKey) {
|
|
107
|
+
if (!isValidPublicKey(publicKey)) {
|
|
108
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', '公钥非法(需 33 字节压缩或 65 字节非压缩,且点必须在曲线上)');
|
|
109
|
+
}
|
|
110
|
+
if (publicKey.length === exports.COMPRESSED_PUBLIC_KEY_LENGTH)
|
|
111
|
+
return Uint8Array.from(publicKey);
|
|
112
|
+
return secp256k1_1.secp256k1.Point.fromHex(publicKey).toBytes(true);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=keys.js.map
|
package/dist/keys.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
|