@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/prf.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PRF 硬件解锁(SDK 方案 §3.3.5)—— 用平台认证器持有的秘密包住解锁密钥。
|
|
4
|
+
*
|
|
5
|
+
* ## 它解决什么问题、不解决什么问题
|
|
6
|
+
*
|
|
7
|
+
* ✅ 解决:把"解锁 keystore 的钥匙"绑到**硬件 + 生物识别/PIN**。秘密由
|
|
8
|
+
* Secure Enclave / Android Keystore(StrongBox)持有,**永不导出**,
|
|
9
|
+
* 每次取都要过生物识别。我们不存它、也导不出它。
|
|
10
|
+
*
|
|
11
|
+
* ❌ 不解决(**别往这个方向设计**):拿 SE/Keystore 直接签链上交易。
|
|
12
|
+
* 浏览器只有 WebAuthn,而 WebAuthn 断言签的是"认证器数据 + 客户端数据哈希",
|
|
13
|
+
* **不能对任意 payload 签名** —— 拿不到一把能签 Cosmos 交易的密钥。
|
|
14
|
+
* (出处:SDK 方案 §3.3.5 的 ⚠️ 段,这是本节最容易被想歪的地方。)
|
|
15
|
+
*
|
|
16
|
+
* ## 包装格式(v1,跨语言可复算)
|
|
17
|
+
*
|
|
18
|
+
* ```
|
|
19
|
+
* KEK = HKDF-SHA256(ikm = PRF 秘密, salt = salt, info = info) → 32 字节
|
|
20
|
+
* wrapped = AES-256-GCM(key = KEK, iv = 12 随机字节, plaintext = 被包的密钥材料)
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* ```jsonc
|
|
24
|
+
* {
|
|
25
|
+
* "version": 1,
|
|
26
|
+
* "kdf": "hkdf-sha256",
|
|
27
|
+
* "credentialId": "<WebAuthn 凭据 ID(base64url)>",
|
|
28
|
+
* "salt": "<base64>", // 同时是 HKDF 的 salt 与 AAD 的一部分
|
|
29
|
+
* "info": "trustbase/prf-wrap/v1",
|
|
30
|
+
* "cipher": "aes-256-gcm",
|
|
31
|
+
* "iv": "<base64, 12 字节>",
|
|
32
|
+
* "ciphertext": "<base64>",
|
|
33
|
+
* "tag": "<base64>"
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* AAD(防"参数被改"):
|
|
38
|
+
*
|
|
39
|
+
* ```
|
|
40
|
+
* trustbase-prf-wrap-v1|kdf=hkdf-sha256|credentialId=<id>|info=<info>|salt=<b64>|cipher=aes-256-gcm|iv=<b64>
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* 于是:**换设备/重装 → PRF 秘密变了 → 认证失败 → 回退口令或助记词**
|
|
44
|
+
* (SDK 方案 §3.3.7 第 2 条的取舍:PRF 是"首选体验",不是唯一出路;
|
|
45
|
+
* 双轨的编排见 envelope.ts)。
|
|
46
|
+
*
|
|
47
|
+
* ## 本文件与 prf-webauthn.ts 的分工
|
|
48
|
+
*
|
|
49
|
+
* 本文件是**纯密码学 + 接口**,在 Node 里能完整单测(用 `createStaticPrfProvider`
|
|
50
|
+
* 造假秘密)。真正调 `navigator.credentials` 的适配器在 prf-webauthn.ts,
|
|
51
|
+
* 那个文件**没有 Node 单测覆盖**,必须在真机浏览器验证 —— 那里写得很明确,别混。
|
|
52
|
+
*/
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.PRF_WRAP_KDF = exports.PRF_WRAP_CIPHER = exports.PRF_SALT_DEFAULT_LENGTH = exports.PRF_SALT_MAX_LENGTH = exports.PRF_SALT_MIN_LENGTH = exports.PRF_SECRET_MAX_LENGTH = exports.PRF_SECRET_MIN_LENGTH = exports.PRF_SECRET_LENGTH = exports.PRF_WRAP_AAD_PREFIX = exports.PRF_WRAP_INFO = exports.PRF_WRAP_VERSION = void 0;
|
|
55
|
+
exports.derivePrfKek = derivePrfKek;
|
|
56
|
+
exports.prfWrapAadText = prfWrapAadText;
|
|
57
|
+
exports.wrapKeyWithPrf = wrapKeyWithPrf;
|
|
58
|
+
exports.unwrapKeyWithPrf = unwrapKeyWithPrf;
|
|
59
|
+
exports.parsePrfWrappedKey = parsePrfWrappedKey;
|
|
60
|
+
exports.isPrfWrappedKey = isPrfWrappedKey;
|
|
61
|
+
exports.serializePrfWrappedKey = serializePrfWrappedKey;
|
|
62
|
+
exports.createStaticPrfProvider = createStaticPrfProvider;
|
|
63
|
+
exports.isSameSecret = isSameSecret;
|
|
64
|
+
const hkdf_1 = require("@noble/hashes/hkdf");
|
|
65
|
+
const sha256_1 = require("@noble/hashes/sha256");
|
|
66
|
+
const bytes_1 = require("./bytes");
|
|
67
|
+
const errors_1 = require("./errors");
|
|
68
|
+
const webcrypto_1 = require("./webcrypto");
|
|
69
|
+
/** 包装格式版本 */
|
|
70
|
+
exports.PRF_WRAP_VERSION = 1;
|
|
71
|
+
/** HKDF 的 info:域分隔串(带版本;换格式必须换它,否则新旧包会互相解开对方的东西) */
|
|
72
|
+
exports.PRF_WRAP_INFO = 'trustbase/prf-wrap/v1';
|
|
73
|
+
/** AAD 前缀(版本化) */
|
|
74
|
+
exports.PRF_WRAP_AAD_PREFIX = 'trustbase-prf-wrap-v1';
|
|
75
|
+
/** HKDF-SHA256 在 WebAuthn PRF 下的典型输出长度(认证器返回 32 字节) */
|
|
76
|
+
exports.PRF_SECRET_LENGTH = 32;
|
|
77
|
+
/** 可接受的 PRF 秘密长度区间(给非标准认证器留余量,但拒绝明显不对的输入) */
|
|
78
|
+
exports.PRF_SECRET_MIN_LENGTH = 16;
|
|
79
|
+
exports.PRF_SECRET_MAX_LENGTH = 64;
|
|
80
|
+
/** HKDF salt 长度区间;默认 16 字节随机 */
|
|
81
|
+
exports.PRF_SALT_MIN_LENGTH = 8;
|
|
82
|
+
exports.PRF_SALT_MAX_LENGTH = 64;
|
|
83
|
+
exports.PRF_SALT_DEFAULT_LENGTH = 16;
|
|
84
|
+
/** 唯一支持的对称加密 */
|
|
85
|
+
exports.PRF_WRAP_CIPHER = 'aes-256-gcm';
|
|
86
|
+
/** 唯一支持的 KDF */
|
|
87
|
+
exports.PRF_WRAP_KDF = 'hkdf-sha256';
|
|
88
|
+
function assertPrfSecret(prfSecret) {
|
|
89
|
+
if (!(prfSecret instanceof Uint8Array)) {
|
|
90
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', 'PRF 秘密必须是 Uint8Array');
|
|
91
|
+
}
|
|
92
|
+
if (prfSecret.length < exports.PRF_SECRET_MIN_LENGTH || prfSecret.length > exports.PRF_SECRET_MAX_LENGTH) {
|
|
93
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', `PRF 秘密长度必须在 ${exports.PRF_SECRET_MIN_LENGTH}..${exports.PRF_SECRET_MAX_LENGTH} 字节之间` +
|
|
94
|
+
`(WebAuthn 的 PRF 扩展通常给 ${exports.PRF_SECRET_LENGTH} 字节)`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function assertCredentialId(credentialId) {
|
|
98
|
+
if (typeof credentialId !== 'string' || credentialId.length === 0) {
|
|
99
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', 'credentialId 必须是非空字符串(WebAuthn 凭据 ID,base64url)');
|
|
100
|
+
}
|
|
101
|
+
if (credentialId.includes('|')) {
|
|
102
|
+
// AAD 用 `|` 做分隔符:如果 ID 里可以有 `|`,就能构造出两份不同的字段组合、
|
|
103
|
+
// 同一段 AAD 文本 —— 那正是 AAD 想防的东西
|
|
104
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', 'credentialId 不能包含 `|`(会破坏 AAD 的分隔语义)');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function resolveSalt(salt) {
|
|
108
|
+
const resolved = salt ?? (0, webcrypto_1.randomBytes)(exports.PRF_SALT_DEFAULT_LENGTH);
|
|
109
|
+
if (!(resolved instanceof Uint8Array)) {
|
|
110
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', 'salt 必须是 Uint8Array');
|
|
111
|
+
}
|
|
112
|
+
if (resolved.length < exports.PRF_SALT_MIN_LENGTH || resolved.length > exports.PRF_SALT_MAX_LENGTH) {
|
|
113
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', `salt 长度必须在 ${exports.PRF_SALT_MIN_LENGTH}..${exports.PRF_SALT_MAX_LENGTH} 字节之间`);
|
|
114
|
+
}
|
|
115
|
+
return resolved;
|
|
116
|
+
}
|
|
117
|
+
function resolveInfo(info) {
|
|
118
|
+
const resolved = info ?? exports.PRF_WRAP_INFO;
|
|
119
|
+
if (typeof resolved !== 'string' || resolved.length === 0 || resolved.includes('|')) {
|
|
120
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', 'info 必须是非空字符串且不含 `|`');
|
|
121
|
+
}
|
|
122
|
+
return resolved;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* HKDF-SHA256:`PRF 秘密 → 32 字节 KEK`(RFC 5869)。
|
|
126
|
+
*
|
|
127
|
+
* 用 `@noble/hashes/hkdf` 而不是 WebCrypto 的 HKDF:这条路径必须**确定性可复算**
|
|
128
|
+
* (`test/prf.test.ts` 里用 RFC 5869 的公开向量与 `node:crypto.hkdfSync` 双侧对照),
|
|
129
|
+
* 而且 PRF 本身已经是硬件门控的,不差平台这一道。
|
|
130
|
+
*/
|
|
131
|
+
function derivePrfKek(prfSecret, salt, info = exports.PRF_WRAP_INFO) {
|
|
132
|
+
assertPrfSecret(prfSecret);
|
|
133
|
+
if (!(salt instanceof Uint8Array) || salt.length === 0) {
|
|
134
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', 'HKDF salt 不能为空');
|
|
135
|
+
}
|
|
136
|
+
return (0, hkdf_1.hkdf)(sha256_1.sha256, prfSecret, salt, (0, bytes_1.utf8ToBytes)(info), 32);
|
|
137
|
+
}
|
|
138
|
+
/** 被包的头的 AAD 文本(格式见文件头;由测试用字面量钉住) */
|
|
139
|
+
function prfWrapAadText(wrapped) {
|
|
140
|
+
return [
|
|
141
|
+
exports.PRF_WRAP_AAD_PREFIX,
|
|
142
|
+
`kdf=${wrapped.kdf}`,
|
|
143
|
+
`credentialId=${wrapped.credentialId}`,
|
|
144
|
+
`info=${wrapped.info}`,
|
|
145
|
+
`salt=${wrapped.salt}`,
|
|
146
|
+
`cipher=${wrapped.cipher}`,
|
|
147
|
+
`iv=${wrapped.iv}`,
|
|
148
|
+
].join('|');
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 用 PRF 秘密包住密钥材料(通常是账户的数据密钥 DEK)。
|
|
152
|
+
*
|
|
153
|
+
* **不修改 `keyMaterial`**(调用方的缓冲区);内部 KEK 在返回前清零。
|
|
154
|
+
*/
|
|
155
|
+
async function wrapKeyWithPrf(prfSecret, keyMaterial, options) {
|
|
156
|
+
assertPrfSecret(prfSecret);
|
|
157
|
+
if (!(keyMaterial instanceof Uint8Array) || keyMaterial.length === 0) {
|
|
158
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', '被包装的密钥材料必须是非空 Uint8Array');
|
|
159
|
+
}
|
|
160
|
+
assertCredentialId(options.credentialId);
|
|
161
|
+
const salt = resolveSalt(options.salt);
|
|
162
|
+
const info = resolveInfo(options.info);
|
|
163
|
+
const kek = derivePrfKek(prfSecret, salt, info);
|
|
164
|
+
try {
|
|
165
|
+
const iv = (0, bytes_1.base64Encode)((0, webcrypto_1.randomBytes)(webcrypto_1.AES_GCM_IV_LENGTH));
|
|
166
|
+
const header = {
|
|
167
|
+
kdf: exports.PRF_WRAP_KDF,
|
|
168
|
+
credentialId: options.credentialId,
|
|
169
|
+
info,
|
|
170
|
+
salt: (0, bytes_1.base64Encode)(salt),
|
|
171
|
+
cipher: exports.PRF_WRAP_CIPHER,
|
|
172
|
+
iv,
|
|
173
|
+
};
|
|
174
|
+
const sealed = await (0, webcrypto_1.aesGcmEncrypt)(kek, (0, bytes_1.base64Decode)(iv), keyMaterial, (0, bytes_1.utf8ToBytes)(prfWrapAadText(header)));
|
|
175
|
+
return {
|
|
176
|
+
version: exports.PRF_WRAP_VERSION,
|
|
177
|
+
...header,
|
|
178
|
+
ciphertext: (0, bytes_1.base64Encode)(sealed.ciphertext),
|
|
179
|
+
tag: (0, bytes_1.base64Encode)(sealed.tag),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
(0, bytes_1.wipe)(kek);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 用 PRF 秘密拆开包装,拿回密钥材料。
|
|
188
|
+
*
|
|
189
|
+
* PRF 秘密不对(换设备、重装、凭据被删)→ `AUTH_FAILED`,
|
|
190
|
+
* 与"口令错"同一句话:调用方应当据此提示"用口令或助记词解锁"。
|
|
191
|
+
*/
|
|
192
|
+
async function unwrapKeyWithPrf(wrapped, prfSecret) {
|
|
193
|
+
const record = parsePrfWrappedKey(wrapped);
|
|
194
|
+
assertPrfSecret(prfSecret);
|
|
195
|
+
const kek = derivePrfKek(prfSecret, (0, bytes_1.base64Decode)(record.salt), record.info);
|
|
196
|
+
try {
|
|
197
|
+
return await (0, webcrypto_1.aesGcmDecrypt)(kek, (0, bytes_1.base64Decode)(record.iv), (0, bytes_1.base64Decode)(record.ciphertext), (0, bytes_1.base64Decode)(record.tag), (0, bytes_1.utf8ToBytes)(prfWrapAadText(record)));
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
(0, bytes_1.wipe)(kek);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const PRF_WRAP_KEYS = [
|
|
204
|
+
'version',
|
|
205
|
+
'kdf',
|
|
206
|
+
'credentialId',
|
|
207
|
+
'salt',
|
|
208
|
+
'info',
|
|
209
|
+
'cipher',
|
|
210
|
+
'iv',
|
|
211
|
+
'ciphertext',
|
|
212
|
+
'tag',
|
|
213
|
+
];
|
|
214
|
+
/** 校验并归一化一份 PRF 包装(接受对象或 JSON 字符串);不合法抛 `INVALID_BLOB` 等 */
|
|
215
|
+
function parsePrfWrappedKey(value) {
|
|
216
|
+
let raw = value;
|
|
217
|
+
if (typeof value === 'string') {
|
|
218
|
+
try {
|
|
219
|
+
raw = JSON.parse(value);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
throw new errors_1.AccountError('INVALID_BLOB', 'PRF 包装不是合法 JSON');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
226
|
+
throw new errors_1.AccountError('INVALID_BLOB', 'PRF 包装必须是对象');
|
|
227
|
+
}
|
|
228
|
+
const record = raw;
|
|
229
|
+
for (const key of PRF_WRAP_KEYS) {
|
|
230
|
+
if (!(key in record))
|
|
231
|
+
throw new errors_1.AccountError('INVALID_BLOB', `PRF 包装缺少字段:${key}`);
|
|
232
|
+
}
|
|
233
|
+
for (const key of Object.keys(record)) {
|
|
234
|
+
if (!PRF_WRAP_KEYS.includes(key)) {
|
|
235
|
+
throw new errors_1.AccountError('INVALID_BLOB', `PRF 包装含未知字段:${key}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (record.version !== exports.PRF_WRAP_VERSION) {
|
|
239
|
+
// 只有"整数且比本实现新"才算版本问题;1.5 / "1" 一律是结构非法
|
|
240
|
+
if (typeof record.version === 'number' && Number.isInteger(record.version) && record.version > exports.PRF_WRAP_VERSION) {
|
|
241
|
+
throw new errors_1.AccountError('UNSUPPORTED_VERSION', `PRF 包装版本 ${record.version} 本实现不认识`);
|
|
242
|
+
}
|
|
243
|
+
throw new errors_1.AccountError('INVALID_BLOB', `PRF 包装的 version 非法:${String(record.version)}`);
|
|
244
|
+
}
|
|
245
|
+
if (record.kdf !== exports.PRF_WRAP_KDF) {
|
|
246
|
+
throw new errors_1.AccountError('UNSUPPORTED_KDF', `PRF 包装的 kdf 不支持:${String(record.kdf)}`);
|
|
247
|
+
}
|
|
248
|
+
if (record.cipher !== exports.PRF_WRAP_CIPHER) {
|
|
249
|
+
throw new errors_1.AccountError('INVALID_BLOB', `PRF 包装的 cipher 非法:${String(record.cipher)}`);
|
|
250
|
+
}
|
|
251
|
+
assertCredentialId(record.credentialId);
|
|
252
|
+
if (typeof record.info !== 'string' || record.info.length === 0 || record.info.includes('|')) {
|
|
253
|
+
throw new errors_1.AccountError('INVALID_BLOB', 'PRF 包装的 info 非法');
|
|
254
|
+
}
|
|
255
|
+
(0, bytes_1.decodeBlobField)(record.salt, 'PRF 包装的 salt', { min: exports.PRF_SALT_MIN_LENGTH, max: exports.PRF_SALT_MAX_LENGTH });
|
|
256
|
+
(0, bytes_1.decodeBlobField)(record.iv, 'PRF 包装的 iv', { exact: webcrypto_1.AES_GCM_IV_LENGTH });
|
|
257
|
+
(0, bytes_1.decodeBlobField)(record.tag, 'PRF 包装的 tag', { exact: webcrypto_1.AES_GCM_TAG_LENGTH });
|
|
258
|
+
(0, bytes_1.decodeBlobField)(record.ciphertext, 'PRF 包装的 ciphertext', { min: 1 });
|
|
259
|
+
return record;
|
|
260
|
+
}
|
|
261
|
+
/** 只问"能不能当 PRF 包装用",不抛异常 */
|
|
262
|
+
function isPrfWrappedKey(value) {
|
|
263
|
+
try {
|
|
264
|
+
parsePrfWrappedKey(value);
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** 序列化(字段顺序固定,便于跨端逐字节比对) */
|
|
272
|
+
function serializePrfWrappedKey(wrapped) {
|
|
273
|
+
const record = parsePrfWrappedKey(wrapped);
|
|
274
|
+
return JSON.stringify({
|
|
275
|
+
version: record.version,
|
|
276
|
+
kdf: record.kdf,
|
|
277
|
+
credentialId: record.credentialId,
|
|
278
|
+
salt: record.salt,
|
|
279
|
+
info: record.info,
|
|
280
|
+
cipher: record.cipher,
|
|
281
|
+
iv: record.iv,
|
|
282
|
+
ciphertext: record.ciphertext,
|
|
283
|
+
tag: record.tag,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* 用固定秘密伪造一个 `PrfProvider`。
|
|
288
|
+
*
|
|
289
|
+
* ⚠️ **仅供测试与本地开发**:它没有任何硬件保护、不要求生物识别,秘密就在
|
|
290
|
+
* 进程内存里。生产环境必须用 prf-webauthn.ts 的真适配器(或原生侧的对应实现)。
|
|
291
|
+
* 之所以放在 src 而不是 test:它是"接口可注入"这条设计的证明,
|
|
292
|
+
* 也让上层(PWA 的降级路径、CLI 的自动化)有一个明确的替身可用。
|
|
293
|
+
*/
|
|
294
|
+
function createStaticPrfProvider(secret, options = {}) {
|
|
295
|
+
assertPrfSecret(secret);
|
|
296
|
+
const expected = Uint8Array.from(secret);
|
|
297
|
+
const available = options.available ?? true;
|
|
298
|
+
return {
|
|
299
|
+
kind: options.kind ?? 'static',
|
|
300
|
+
async isAvailable() {
|
|
301
|
+
return available;
|
|
302
|
+
},
|
|
303
|
+
async getPrfSecret(credentialId, salt) {
|
|
304
|
+
assertCredentialId(credentialId);
|
|
305
|
+
if (!available) {
|
|
306
|
+
throw new errors_1.AccountError('PRF_UNAVAILABLE', '该 provider 被标记为不可用(测试替身)');
|
|
307
|
+
}
|
|
308
|
+
// 真实认证器返回的是"由凭据秘密与 salt 共同决定的固定值";这里用
|
|
309
|
+
// HKDF 复现同样的性质:同一个 (credentialId, salt) 永远得到同一串秘密。
|
|
310
|
+
return derivePrfKek(expected, salt, `static-provider/${credentialId}`);
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/** 常数时间比对两份 PRF 秘密(给适配器做"秘密是否变化"自检用) */
|
|
315
|
+
function isSameSecret(a, b) {
|
|
316
|
+
return (0, bytes_1.equalBytes)(a, b);
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=prf.js.map
|
package/dist/prf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prf.js","sourceRoot":"","sources":["../src/prf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCrypto 访问层 —— 本包**唯一**碰平台密码学的地方。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么是 WebCrypto 而不是自造或 Node 的 `crypto`
|
|
5
|
+
*
|
|
6
|
+
* - SDK 方案 §3.3.1 定的就是「AES-256-GCM(WebCrypto 原生)」,随机数只用
|
|
7
|
+
* `crypto.getRandomValues`;Node 18+ 也有同一套 API(`globalThis.crypto.subtle`),
|
|
8
|
+
* 所以同构不需要两套实现。
|
|
9
|
+
* - 自造 AES-GCM 或走 `node:crypto` 都会把浏览器侧搞死(同构守卫直接拦)。
|
|
10
|
+
*
|
|
11
|
+
* ## 为什么手写这几个 `…Like` 类型,而不是开 `lib: ["DOM"]`
|
|
12
|
+
*
|
|
13
|
+
* 本包(与 `@trustbase/protocol` 一样)只开 `lib: ["ES2022"]`。一旦开 DOM lib,
|
|
14
|
+
* `document` / `window` / `fetch` / `localStorage` 全都变成"能通过类型检查"的
|
|
15
|
+
* 全局 —— 而它们**在 Node 里跑测试时不存在**:错误就从"编译期"漂到"运行时",
|
|
16
|
+
* 而且只在上线后某个环境炸。这里只声明我们真正用到的那几个成员,
|
|
17
|
+
* 多一个都不给(调用方也别想从本模块借到 `document` 的类型)。
|
|
18
|
+
*
|
|
19
|
+
* ## 认证失败为什么在这里被"压平"
|
|
20
|
+
*
|
|
21
|
+
* `subtle.decrypt` 在 GCM 标签不匹配时抛 `OperationError`。我们把它**统一**
|
|
22
|
+
* 转换成 `AUTH_FAILED`(口令错 / 密文被改 / PRF 秘密不匹配都用这一个码),
|
|
23
|
+
* 理由见 errors.ts 文件头。反过来,**参数形状错误**(key 不是 32 字节、
|
|
24
|
+
* iv 不是 12 字节、tag 不是 16 字节)是我们自己代码的 bug,抛
|
|
25
|
+
* `INVALID_ARGUMENT` —— 这种错必须一眼看出是编程错误,不能被伪装成"认证失败"。
|
|
26
|
+
*/
|
|
27
|
+
/** GCM 推荐 IV 长度(96 bit)—— NIST SP 800-38D 的规定长度,别的长度要多一次 GHASH */
|
|
28
|
+
export declare const AES_GCM_IV_LENGTH = 12;
|
|
29
|
+
/** GCM 认证标签长度(128 bit,WebCrypto 默认,也是最强) */
|
|
30
|
+
export declare const AES_GCM_TAG_LENGTH = 16;
|
|
31
|
+
/** AES-256 密钥长度 */
|
|
32
|
+
export declare const AES_KEY_LENGTH = 32;
|
|
33
|
+
/** WebCrypto 密钥句柄(只声明我们需要读的成员) */
|
|
34
|
+
export interface CryptoKeyLike {
|
|
35
|
+
readonly type: string;
|
|
36
|
+
readonly extractable: boolean;
|
|
37
|
+
readonly usages: readonly string[];
|
|
38
|
+
}
|
|
39
|
+
interface AesGcmParamsLike {
|
|
40
|
+
readonly name: 'AES-GCM';
|
|
41
|
+
readonly iv: Uint8Array;
|
|
42
|
+
readonly tagLength: number;
|
|
43
|
+
readonly additionalData?: Uint8Array;
|
|
44
|
+
}
|
|
45
|
+
/** `crypto.subtle` 中本包用到的成员(AES-GCM 加解密) */
|
|
46
|
+
export interface SubtleCryptoLike {
|
|
47
|
+
importKey(format: 'raw', keyData: Uint8Array, algorithm: {
|
|
48
|
+
readonly name: string;
|
|
49
|
+
}, extractable: boolean, keyUsages: readonly string[]): Promise<CryptoKeyLike>;
|
|
50
|
+
encrypt(algorithm: AesGcmParamsLike, key: CryptoKeyLike, data: Uint8Array): Promise<ArrayBuffer>;
|
|
51
|
+
decrypt(algorithm: AesGcmParamsLike, key: CryptoKeyLike, data: Uint8Array): Promise<ArrayBuffer>;
|
|
52
|
+
}
|
|
53
|
+
/** `globalThis.crypto` 中本包用到的成员 */
|
|
54
|
+
export interface CryptoLike {
|
|
55
|
+
readonly subtle: SubtleCryptoLike;
|
|
56
|
+
getRandomValues<T extends Uint8Array>(array: T): T;
|
|
57
|
+
}
|
|
58
|
+
/** 运行环境是否有可用的 WebCrypto(含 `subtle` 与 `getRandomValues`) */
|
|
59
|
+
export declare function isWebCryptoAvailable(): boolean;
|
|
60
|
+
/** 取 `crypto.subtle`;不可用时抛 `CRYPTO_UNAVAILABLE`(附可读原因) */
|
|
61
|
+
export declare function requireSubtle(): SubtleCryptoLike;
|
|
62
|
+
/**
|
|
63
|
+
* 密码学随机字节。
|
|
64
|
+
*
|
|
65
|
+
* **绝不用 `Math.random`**(SDK 方案 §3.3.1 明写)—— 这里的实现只走
|
|
66
|
+
* `crypto.getRandomValues`,拿不到随机源就抛 `CRYPTO_UNAVAILABLE`,
|
|
67
|
+
* 不存在"退化为弱随机"的分支。这一点很关键:退化分支一旦存在,
|
|
68
|
+
* 就一定会有人在某个环境里静默用上它。
|
|
69
|
+
*/
|
|
70
|
+
export declare function randomBytes(length: number): Uint8Array;
|
|
71
|
+
/**
|
|
72
|
+
* AES-256-GCM 加密。
|
|
73
|
+
*
|
|
74
|
+
* `aad`(additional authenticated data)用来把**明文头**(blob 版本 / KDF 名字 /
|
|
75
|
+
* KDF 参数 / IV)绑进认证范围:这些字段不参与密钥派生,若不认证,攻击者可以
|
|
76
|
+
* 改掉它们而密文照样解得开 —— 那就等于"KDF 参数随人改"。见 keystore.ts 的
|
|
77
|
+
* `keystoreAad()`。
|
|
78
|
+
*
|
|
79
|
+
* WebCrypto 返回的是 `ciphertext || tag` 连在一起的缓冲,这里按设计文档的
|
|
80
|
+
* 字段形状拆成两段(`ciphertext` / `tag`),方便别的语言/别的实现对照复算。
|
|
81
|
+
*/
|
|
82
|
+
export declare function aesGcmEncrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Promise<{
|
|
83
|
+
ciphertext: Uint8Array;
|
|
84
|
+
tag: Uint8Array;
|
|
85
|
+
}>;
|
|
86
|
+
/**
|
|
87
|
+
* AES-256-GCM 解密。
|
|
88
|
+
*
|
|
89
|
+
* 认证失败(口令错 / 密文被改 / 秘密不匹配)→ `AUTH_FAILED`,消息刻意含糊。
|
|
90
|
+
*/
|
|
91
|
+
export declare function aesGcmDecrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array, tag: Uint8Array, aad?: Uint8Array): Promise<Uint8Array>;
|
|
92
|
+
export {};
|
|
93
|
+
//# sourceMappingURL=webcrypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webcrypto.d.ts","sourceRoot":"","sources":["../src/webcrypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,4CAA4C;AAC5C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,mBAAmB;AACnB,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,kCAAkC;AAClC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,UAAU,gBAAgB;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;CACtC;AAED,4CAA4C;AAC5C,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CACP,MAAM,EAAE,KAAK,EACb,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EACpC,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1B,OAAO,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACjG,OAAO,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAClG;AAED,mCAAmC;AACnC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;CACpD;AAQD,2DAA2D;AAC3D,wBAAgB,oBAAoB,IAAI,OAAO,CAQ9C;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,IAAI,gBAAgB,CAUhD;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAetD;AAsBD;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,EAAE,EAAE,UAAU,EACd,SAAS,EAAE,UAAU,EACrB,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAAE,CAAC,CAkBtD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,UAAU,EACf,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC,UAAU,CAAC,CAuBrB"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WebCrypto 访问层 —— 本包**唯一**碰平台密码学的地方。
|
|
4
|
+
*
|
|
5
|
+
* ## 为什么是 WebCrypto 而不是自造或 Node 的 `crypto`
|
|
6
|
+
*
|
|
7
|
+
* - SDK 方案 §3.3.1 定的就是「AES-256-GCM(WebCrypto 原生)」,随机数只用
|
|
8
|
+
* `crypto.getRandomValues`;Node 18+ 也有同一套 API(`globalThis.crypto.subtle`),
|
|
9
|
+
* 所以同构不需要两套实现。
|
|
10
|
+
* - 自造 AES-GCM 或走 `node:crypto` 都会把浏览器侧搞死(同构守卫直接拦)。
|
|
11
|
+
*
|
|
12
|
+
* ## 为什么手写这几个 `…Like` 类型,而不是开 `lib: ["DOM"]`
|
|
13
|
+
*
|
|
14
|
+
* 本包(与 `@trustbase/protocol` 一样)只开 `lib: ["ES2022"]`。一旦开 DOM lib,
|
|
15
|
+
* `document` / `window` / `fetch` / `localStorage` 全都变成"能通过类型检查"的
|
|
16
|
+
* 全局 —— 而它们**在 Node 里跑测试时不存在**:错误就从"编译期"漂到"运行时",
|
|
17
|
+
* 而且只在上线后某个环境炸。这里只声明我们真正用到的那几个成员,
|
|
18
|
+
* 多一个都不给(调用方也别想从本模块借到 `document` 的类型)。
|
|
19
|
+
*
|
|
20
|
+
* ## 认证失败为什么在这里被"压平"
|
|
21
|
+
*
|
|
22
|
+
* `subtle.decrypt` 在 GCM 标签不匹配时抛 `OperationError`。我们把它**统一**
|
|
23
|
+
* 转换成 `AUTH_FAILED`(口令错 / 密文被改 / PRF 秘密不匹配都用这一个码),
|
|
24
|
+
* 理由见 errors.ts 文件头。反过来,**参数形状错误**(key 不是 32 字节、
|
|
25
|
+
* iv 不是 12 字节、tag 不是 16 字节)是我们自己代码的 bug,抛
|
|
26
|
+
* `INVALID_ARGUMENT` —— 这种错必须一眼看出是编程错误,不能被伪装成"认证失败"。
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.AES_KEY_LENGTH = exports.AES_GCM_TAG_LENGTH = exports.AES_GCM_IV_LENGTH = void 0;
|
|
30
|
+
exports.isWebCryptoAvailable = isWebCryptoAvailable;
|
|
31
|
+
exports.requireSubtle = requireSubtle;
|
|
32
|
+
exports.randomBytes = randomBytes;
|
|
33
|
+
exports.aesGcmEncrypt = aesGcmEncrypt;
|
|
34
|
+
exports.aesGcmDecrypt = aesGcmDecrypt;
|
|
35
|
+
const errors_1 = require("./errors");
|
|
36
|
+
/** GCM 推荐 IV 长度(96 bit)—— NIST SP 800-38D 的规定长度,别的长度要多一次 GHASH */
|
|
37
|
+
exports.AES_GCM_IV_LENGTH = 12;
|
|
38
|
+
/** GCM 认证标签长度(128 bit,WebCrypto 默认,也是最强) */
|
|
39
|
+
exports.AES_GCM_TAG_LENGTH = 16;
|
|
40
|
+
/** AES-256 密钥长度 */
|
|
41
|
+
exports.AES_KEY_LENGTH = 32;
|
|
42
|
+
function globalCrypto() {
|
|
43
|
+
const candidate = globalThis.crypto;
|
|
44
|
+
if (!candidate || typeof candidate !== 'object')
|
|
45
|
+
return undefined;
|
|
46
|
+
return candidate;
|
|
47
|
+
}
|
|
48
|
+
/** 运行环境是否有可用的 WebCrypto(含 `subtle` 与 `getRandomValues`) */
|
|
49
|
+
function isWebCryptoAvailable() {
|
|
50
|
+
const candidate = globalCrypto();
|
|
51
|
+
return (candidate !== undefined &&
|
|
52
|
+
typeof candidate.getRandomValues === 'function' &&
|
|
53
|
+
typeof candidate.subtle === 'object' &&
|
|
54
|
+
candidate.subtle !== null);
|
|
55
|
+
}
|
|
56
|
+
/** 取 `crypto.subtle`;不可用时抛 `CRYPTO_UNAVAILABLE`(附可读原因) */
|
|
57
|
+
function requireSubtle() {
|
|
58
|
+
const candidate = globalCrypto();
|
|
59
|
+
if (!candidate || typeof candidate.subtle !== 'object' || candidate.subtle === null) {
|
|
60
|
+
throw new errors_1.AccountError('CRYPTO_UNAVAILABLE', '本环境没有 WebCrypto(globalThis.crypto.subtle)—— 无法加解密密钥库。' +
|
|
61
|
+
'浏览器请用 https/localhost 打开(非安全上下文不暴露 WebCrypto),Node 请用 18 及以上。');
|
|
62
|
+
}
|
|
63
|
+
return candidate.subtle;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 密码学随机字节。
|
|
67
|
+
*
|
|
68
|
+
* **绝不用 `Math.random`**(SDK 方案 §3.3.1 明写)—— 这里的实现只走
|
|
69
|
+
* `crypto.getRandomValues`,拿不到随机源就抛 `CRYPTO_UNAVAILABLE`,
|
|
70
|
+
* 不存在"退化为弱随机"的分支。这一点很关键:退化分支一旦存在,
|
|
71
|
+
* 就一定会有人在某个环境里静默用上它。
|
|
72
|
+
*/
|
|
73
|
+
function randomBytes(length) {
|
|
74
|
+
if (!Number.isInteger(length) || length < 0) {
|
|
75
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', '随机字节长度必须是非负整数');
|
|
76
|
+
}
|
|
77
|
+
const candidate = globalCrypto();
|
|
78
|
+
if (!candidate || typeof candidate.getRandomValues !== 'function') {
|
|
79
|
+
throw new errors_1.AccountError('CRYPTO_UNAVAILABLE', '本环境没有 crypto.getRandomValues —— 无法生成安全随机数');
|
|
80
|
+
}
|
|
81
|
+
const out = new Uint8Array(length);
|
|
82
|
+
// getRandomValues 对单次请求有 65536 字节上限(超出抛 QuotaExceededError),分块填充
|
|
83
|
+
const MAX_CHUNK = 65_536;
|
|
84
|
+
for (let offset = 0; offset < length; offset += MAX_CHUNK) {
|
|
85
|
+
candidate.getRandomValues(out.subarray(offset, Math.min(offset + MAX_CHUNK, length)));
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/** 断言密钥长度(编程错误,不是认证失败) */
|
|
90
|
+
function assertKeyLength(key) {
|
|
91
|
+
if (!(key instanceof Uint8Array) || key.length !== exports.AES_KEY_LENGTH) {
|
|
92
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', `AES-256-GCM 密钥必须是 ${exports.AES_KEY_LENGTH} 字节`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** 断言 IV 长度 */
|
|
96
|
+
function assertIvLength(iv) {
|
|
97
|
+
if (!(iv instanceof Uint8Array) || iv.length !== exports.AES_GCM_IV_LENGTH) {
|
|
98
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', `AES-GCM 的 IV 必须是 ${exports.AES_GCM_IV_LENGTH} 字节`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function importAesKey(key, usage) {
|
|
102
|
+
assertKeyLength(key);
|
|
103
|
+
// extractable = false:这把句柄再也导不出原始字节,是"少一份副本"的免费收益
|
|
104
|
+
return requireSubtle().importKey('raw', key, { name: 'AES-GCM' }, false, [usage]);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* AES-256-GCM 加密。
|
|
108
|
+
*
|
|
109
|
+
* `aad`(additional authenticated data)用来把**明文头**(blob 版本 / KDF 名字 /
|
|
110
|
+
* KDF 参数 / IV)绑进认证范围:这些字段不参与密钥派生,若不认证,攻击者可以
|
|
111
|
+
* 改掉它们而密文照样解得开 —— 那就等于"KDF 参数随人改"。见 keystore.ts 的
|
|
112
|
+
* `keystoreAad()`。
|
|
113
|
+
*
|
|
114
|
+
* WebCrypto 返回的是 `ciphertext || tag` 连在一起的缓冲,这里按设计文档的
|
|
115
|
+
* 字段形状拆成两段(`ciphertext` / `tag`),方便别的语言/别的实现对照复算。
|
|
116
|
+
*/
|
|
117
|
+
async function aesGcmEncrypt(key, iv, plaintext, aad) {
|
|
118
|
+
assertIvLength(iv);
|
|
119
|
+
if (!(plaintext instanceof Uint8Array)) {
|
|
120
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', '待加密的明文必须是 Uint8Array');
|
|
121
|
+
}
|
|
122
|
+
const cryptoKey = await importAesKey(key, 'encrypt');
|
|
123
|
+
const sealed = new Uint8Array(await requireSubtle().encrypt({ name: 'AES-GCM', iv, tagLength: exports.AES_GCM_TAG_LENGTH * 8, additionalData: aad }, cryptoKey, plaintext));
|
|
124
|
+
if (sealed.length < exports.AES_GCM_TAG_LENGTH) {
|
|
125
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', 'GCM 输出短于认证标签长度 —— 运行时的 WebCrypto 行为异常');
|
|
126
|
+
}
|
|
127
|
+
const tagStart = sealed.length - exports.AES_GCM_TAG_LENGTH;
|
|
128
|
+
return { ciphertext: sealed.slice(0, tagStart), tag: sealed.slice(tagStart) };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* AES-256-GCM 解密。
|
|
132
|
+
*
|
|
133
|
+
* 认证失败(口令错 / 密文被改 / 秘密不匹配)→ `AUTH_FAILED`,消息刻意含糊。
|
|
134
|
+
*/
|
|
135
|
+
async function aesGcmDecrypt(key, iv, ciphertext, tag, aad) {
|
|
136
|
+
assertIvLength(iv);
|
|
137
|
+
if (!(tag instanceof Uint8Array) || tag.length !== exports.AES_GCM_TAG_LENGTH) {
|
|
138
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', `GCM 认证标签必须是 ${exports.AES_GCM_TAG_LENGTH} 字节`);
|
|
139
|
+
}
|
|
140
|
+
if (!(ciphertext instanceof Uint8Array)) {
|
|
141
|
+
throw new errors_1.AccountError('INVALID_ARGUMENT', '密文必须是 Uint8Array');
|
|
142
|
+
}
|
|
143
|
+
const cryptoKey = await importAesKey(key, 'decrypt');
|
|
144
|
+
const sealed = new Uint8Array(ciphertext.length + tag.length);
|
|
145
|
+
sealed.set(ciphertext, 0);
|
|
146
|
+
sealed.set(tag, ciphertext.length);
|
|
147
|
+
try {
|
|
148
|
+
const opened = await requireSubtle().decrypt({ name: 'AES-GCM', iv, tagLength: exports.AES_GCM_TAG_LENGTH * 8, additionalData: aad }, cryptoKey, sealed);
|
|
149
|
+
return new Uint8Array(opened);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (error instanceof errors_1.AccountError)
|
|
153
|
+
throw error;
|
|
154
|
+
throw new errors_1.AccountError('AUTH_FAILED', '解锁失败:口令错误或数据已损坏');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=webcrypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webcrypto.js","sourceRoot":"","sources":["../src/webcrypto.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;;;AAmDH,oDAQC;AAGD,sCAUC;AAUD,kCAeC;AAiCD,sCAuBC;AAOD,sCA6BC;AA3LD,qCAAwC;AAExC,kEAAkE;AACrD,QAAA,iBAAiB,GAAG,EAAE,CAAC;AACpC,4CAA4C;AAC/B,QAAA,kBAAkB,GAAG,EAAE,CAAC;AACrC,mBAAmB;AACN,QAAA,cAAc,GAAG,EAAE,CAAC;AAmCjC,SAAS,YAAY;IACnB,MAAM,SAAS,GAAI,UAAmC,CAAC,MAAM,CAAC;IAC9D,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAClE,OAAO,SAAuB,CAAC;AACjC,CAAC;AAED,2DAA2D;AAC3D,SAAgB,oBAAoB;IAClC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,OAAO,CACL,SAAS,KAAK,SAAS;QACvB,OAAO,SAAS,CAAC,eAAe,KAAK,UAAU;QAC/C,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ;QACpC,SAAS,CAAC,MAAM,KAAK,IAAI,CAC1B,CAAC;AACJ,CAAC;AAED,0DAA0D;AAC1D,SAAgB,aAAa;IAC3B,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QACpF,MAAM,IAAI,qBAAY,CACpB,oBAAoB,EACpB,uDAAuD;YACrD,+DAA+D,CAClE,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,CAAC;AAC1B,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,WAAW,CAAC,MAAc;IACxC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,qBAAY,CAAC,kBAAkB,EAAE,eAAe,CAAC,CAAC;IAC9D,CAAC;IACD,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,CAAC,eAAe,KAAK,UAAU,EAAE,CAAC;QAClE,MAAM,IAAI,qBAAY,CAAC,oBAAoB,EAAE,2CAA2C,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACnC,iEAAiE;IACjE,MAAM,SAAS,GAAG,MAAM,CAAC;IACzB,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,SAAS,EAAE,CAAC;QAC1D,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,0BAA0B;AAC1B,SAAS,eAAe,CAAC,GAAe;IACtC,IAAI,CAAC,CAAC,GAAG,YAAY,UAAU,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,sBAAc,EAAE,CAAC;QAClE,MAAM,IAAI,qBAAY,CAAC,kBAAkB,EAAE,qBAAqB,sBAAc,KAAK,CAAC,CAAC;IACvF,CAAC;AACH,CAAC;AAED,eAAe;AACf,SAAS,cAAc,CAAC,EAAc;IACpC,IAAI,CAAC,CAAC,EAAE,YAAY,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,yBAAiB,EAAE,CAAC;QACnE,MAAM,IAAI,qBAAY,CAAC,kBAAkB,EAAE,oBAAoB,yBAAiB,KAAK,CAAC,CAAC;IACzF,CAAC;AACH,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,GAAe,EAAE,KAA4B;IACvE,eAAe,CAAC,GAAG,CAAC,CAAC;IACrB,kDAAkD;IAClD,OAAO,aAAa,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;AACpF,CAAC;AAED;;;;;;;;;;GAUG;AACI,KAAK,UAAU,aAAa,CACjC,GAAe,EACf,EAAc,EACd,SAAqB,EACrB,GAAgB;IAEhB,cAAc,CAAC,EAAE,CAAC,CAAC;IACnB,IAAI,CAAC,CAAC,SAAS,YAAY,UAAU,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,qBAAY,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,IAAI,UAAU,CAC3B,MAAM,aAAa,EAAE,CAAC,OAAO,CAC3B,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,0BAAkB,GAAG,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,EAC/E,SAAS,EACT,SAAS,CACV,CACF,CAAC;IACF,IAAI,MAAM,CAAC,MAAM,GAAG,0BAAkB,EAAE,CAAC;QACvC,MAAM,IAAI,qBAAY,CAAC,kBAAkB,EAAE,uCAAuC,CAAC,CAAC;IACtF,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,GAAG,0BAAkB,CAAC;IACpD,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,aAAa,CACjC,GAAe,EACf,EAAc,EACd,UAAsB,EACtB,GAAe,EACf,GAAgB;IAEhB,cAAc,CAAC,EAAE,CAAC,CAAC;IACnB,IAAI,CAAC,CAAC,GAAG,YAAY,UAAU,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,0BAAkB,EAAE,CAAC;QACtE,MAAM,IAAI,qBAAY,CAAC,kBAAkB,EAAE,eAAe,0BAAkB,KAAK,CAAC,CAAC;IACrF,CAAC;IACD,IAAI,CAAC,CAAC,UAAU,YAAY,UAAU,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,qBAAY,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9D,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAC,OAAO,CAC1C,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,0BAAkB,GAAG,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,EAC/E,SAAS,EACT,MAAM,CACP,CAAC;QACF,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,qBAAY;YAAE,MAAM,KAAK,CAAC;QAC/C,MAAM,IAAI,qBAAY,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trustbaseai/account",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TrustBase 账户与密钥库:tct1… 地址派生、口令加密 keystore(Argon2id + AES-256-GCM)、WebAuthn PRF 硬件解锁、口令强度与 BIP39 助记词",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"trustbase",
|
|
21
|
+
"account",
|
|
22
|
+
"keystore",
|
|
23
|
+
"argon2id",
|
|
24
|
+
"aes-gcm",
|
|
25
|
+
"webauthn",
|
|
26
|
+
"prf",
|
|
27
|
+
"bip39",
|
|
28
|
+
"bech32"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.build.json",
|
|
32
|
+
"test": "jest",
|
|
33
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
34
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@noble/curves": "^1.9.7",
|
|
38
|
+
"@noble/hashes": "^1.8.0",
|
|
39
|
+
"@scure/bip32": "^1.7.0",
|
|
40
|
+
"@scure/bip39": "^1.6.0",
|
|
41
|
+
"@zxcvbn-ts/core": "^4.2.0",
|
|
42
|
+
"@zxcvbn-ts/language-common": "^4.1.3",
|
|
43
|
+
"hash-wasm": "^4.12.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@cosmjs/amino": "^0.36.2",
|
|
47
|
+
"@cosmjs/crypto": "^0.36.2",
|
|
48
|
+
"@cosmjs/encoding": "^0.36.2",
|
|
49
|
+
"@cosmjs/proto-signing": "^0.36.2"
|
|
50
|
+
}
|
|
51
|
+
}
|