@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.
Files changed (62) hide show
  1. package/README.md +351 -0
  2. package/dist/address.d.ts +70 -0
  3. package/dist/address.d.ts.map +1 -0
  4. package/dist/address.js +104 -0
  5. package/dist/address.js.map +1 -0
  6. package/dist/bech32.d.ts +78 -0
  7. package/dist/bech32.d.ts.map +1 -0
  8. package/dist/bech32.js +246 -0
  9. package/dist/bech32.js.map +1 -0
  10. package/dist/bytes.d.ts +88 -0
  11. package/dist/bytes.d.ts.map +1 -0
  12. package/dist/bytes.js +215 -0
  13. package/dist/bytes.js.map +1 -0
  14. package/dist/envelope.d.ts +116 -0
  15. package/dist/envelope.d.ts.map +1 -0
  16. package/dist/envelope.js +237 -0
  17. package/dist/envelope.js.map +1 -0
  18. package/dist/errors.d.ts +48 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +41 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/hd.d.ts +85 -0
  23. package/dist/hd.d.ts.map +1 -0
  24. package/dist/hd.js +112 -0
  25. package/dist/hd.js.map +1 -0
  26. package/dist/index.d.ts +60 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +199 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/kdf.d.ts +163 -0
  31. package/dist/kdf.d.ts.map +1 -0
  32. package/dist/kdf.js +351 -0
  33. package/dist/kdf.js.map +1 -0
  34. package/dist/keys.d.ts +55 -0
  35. package/dist/keys.d.ts.map +1 -0
  36. package/dist/keys.js +114 -0
  37. package/dist/keys.js.map +1 -0
  38. package/dist/keystore.d.ts +168 -0
  39. package/dist/keystore.d.ts.map +1 -0
  40. package/dist/keystore.js +340 -0
  41. package/dist/keystore.js.map +1 -0
  42. package/dist/mnemonic.d.ts +84 -0
  43. package/dist/mnemonic.d.ts.map +1 -0
  44. package/dist/mnemonic.js +199 -0
  45. package/dist/mnemonic.js.map +1 -0
  46. package/dist/password.d.ts +79 -0
  47. package/dist/password.d.ts.map +1 -0
  48. package/dist/password.js +237 -0
  49. package/dist/password.js.map +1 -0
  50. package/dist/prf-webauthn.d.ts +85 -0
  51. package/dist/prf-webauthn.d.ts.map +1 -0
  52. package/dist/prf-webauthn.js +204 -0
  53. package/dist/prf-webauthn.js.map +1 -0
  54. package/dist/prf.d.ts +157 -0
  55. package/dist/prf.d.ts.map +1 -0
  56. package/dist/prf.js +318 -0
  57. package/dist/prf.js.map +1 -0
  58. package/dist/webcrypto.d.ts +93 -0
  59. package/dist/webcrypto.d.ts.map +1 -0
  60. package/dist/webcrypto.js +157 -0
  61. package/dist/webcrypto.js.map +1 -0
  62. package/package.json +51 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * 加密 keystore(SDK 方案 §3.3.1)—— **只存密文**,口令不落任何地方。
3
+ *
4
+ * ## 密文形状(格式 v1,跨语言可复算)
5
+ *
6
+ * ```jsonc
7
+ * {
8
+ * "version": 1, // 格式版本;不认识就拒,不猜
9
+ * "kdf": "argon2id", // 或 "pbkdf2"
10
+ * "kdfParams": { "salt": "<base64>", "memory": 19456, "cost": 2,
11
+ * "parallelism": 1, "hashLength": 32, "version": 19 },
12
+ * "cipher": "aes-256-gcm",
13
+ * "iv": "<base64, 12 字节>",
14
+ * "ciphertext": "<base64>",
15
+ * "tag": "<base64, 16 字节 GCM 认证标签>"
16
+ * }
17
+ * ```
18
+ *
19
+ * `pbkdf2` 时 `kdfParams` 是 `{salt, iterations, hashLength, hash:'sha256'}`。
20
+ * 字段顺序固定(`serializeKeystore` 显式按序构造),这样同一份 keystore
21
+ * 在任何实现里序列化出来都一样,便于做金标准向量。
22
+ *
23
+ * ## 为什么 KDF 参数要进 AAD(本实现相对设计文档的一点加固)
24
+ *
25
+ * 设计文档列的字段里,`kdf` / `kdfParams` / `version` / `cipher` / `iv` 都是
26
+ * **明文头**:它们不参与解密,但决定了密钥怎么派生。如果只把密文丢给 GCM,
27
+ * 攻击者可以改掉 `kdfParams.cost`(甚至把 `argon2id` 改成 `pbkdf2`)而密文
28
+ * 照样解得开 —— 那就等于"KDF 强度随人调"。所以这里把整个明文头的**规范化
29
+ * 文本**作为 GCM 的 AAD 一起认证:
30
+ *
31
+ * ```
32
+ * trustbase-keystore-v1|kdf=argon2id|salt=<b64>|memory=19456|cost=2|parallelism=1|hashLength=32|version=19|cipher=aes-256-gcm|iv=<b64>
33
+ * trustbase-keystore-v1|kdf=pbkdf2|salt=<b64>|iterations=600000|hashLength=32|hash=sha256|cipher=aes-256-gcm|iv=<b64>
34
+ * ```
35
+ *
36
+ * 任何一位被改 → 认证失败 → `AUTH_FAILED`(与"口令错"同一句话,见 errors.ts)。
37
+ * 这条格式被 `test/keystore.test.ts` 用字面量钉住,改它等于改密钥库格式。
38
+ *
39
+ * ## 内存卫生(说清楚能做到什么)
40
+ *
41
+ * - 派生密钥、KDF 输入字节、解密出的明文:本包都在 `finally` 里 `wipe()`
42
+ * - **不**提供任何把明文写 `localStorage` / `sessionStorage` / IndexedDB 的路径
43
+ * (持久化是调用方的事:设备账户存 IndexedDB,商户侧存本机文件,都只存密文)
44
+ * - 口令是 JS 字符串,**清不掉**(见 bytes.ts `wipe` 的诚实说明);
45
+ * `Uint8Array` 缓冲区我们能清,但引擎/WASM 内部副本清不到
46
+ *
47
+ * ## 校验失败的分类(调用方要能分支)
48
+ *
49
+ * | 情况 | 错误码 |
50
+ * |---|---|
51
+ * | 不是密文 / 字段缺失 / 字段类型不对 / base64 坏 / 参数越界 | `INVALID_BLOB` |
52
+ * | `version` 比本实现新 | `UNSUPPORTED_VERSION` |
53
+ * | `kdf` / `hash` / Argon2 版本不认 | `UNSUPPORTED_KDF` |
54
+ * | 口令错 / 密文被改 / 头被改 | `AUTH_FAILED`(刻意含糊) |
55
+ * | 环境没有 WebCrypto | `CRYPTO_UNAVAILABLE` |
56
+ * | 口令超长(>128)、secret 形状不对 | `INVALID_ARGUMENT` |
57
+ */
58
+ import { type Argon2idParams, type KdfAlgorithm, type Pbkdf2Params } from './kdf';
59
+ /** 密文格式版本。加字段/换算法都要 bump 它(旧实现会拒收,而不是猜着解) */
60
+ export declare const KEYSTORE_VERSION = 1;
61
+ /** 唯一支持的对称加密 */
62
+ export declare const KEYSTORE_CIPHER = "aes-256-gcm";
63
+ /** AAD 前缀(版本化,见文件头) */
64
+ export declare const KEYSTORE_AAD_PREFIX = "trustbase-keystore-v1";
65
+ /** 密钥材料的长度上限(32B 私钥 / 64B BIP39 种子都够;太长的输入说明用错了 API) */
66
+ export declare const MAX_SECRET_MATERIAL_LENGTH = 256;
67
+ interface KeystoreBlobCommon {
68
+ version: typeof KEYSTORE_VERSION;
69
+ cipher: typeof KEYSTORE_CIPHER;
70
+ /** IV(base64,12 字节) */
71
+ iv: string;
72
+ /** 密文(base64,不含 tag) */
73
+ ciphertext: string;
74
+ /** GCM 认证标签(base64,16 字节) */
75
+ tag: string;
76
+ }
77
+ /**
78
+ * 密文 blob。写成"公共字段 & (算法 + 该算法的参数)"的**判别联合**:
79
+ * `kdf` 与 `kdfParams` 的形状必须配对,写错在编译期就报。
80
+ */
81
+ export type KeystoreBlob = (KeystoreBlobCommon & {
82
+ kdf: 'argon2id';
83
+ kdfParams: Argon2idParams;
84
+ }) | (KeystoreBlobCommon & {
85
+ kdf: 'pbkdf2';
86
+ kdfParams: Pbkdf2Params;
87
+ });
88
+ interface KeystoreHeaderCommon {
89
+ version: typeof KEYSTORE_VERSION;
90
+ cipher: typeof KEYSTORE_CIPHER;
91
+ iv: string;
92
+ }
93
+ /**
94
+ * 明文头 = AAD 的输入。
95
+ *
96
+ * `ciphertext` / `tag` **不在里面**:它们本身就是"被认证的对象",
97
+ * 自己认证自己没有意义。
98
+ */
99
+ export type KeystoreHeader = (KeystoreHeaderCommon & {
100
+ kdf: 'argon2id';
101
+ kdfParams: Argon2idParams;
102
+ }) | (KeystoreHeaderCommon & {
103
+ kdf: 'pbkdf2';
104
+ kdfParams: Pbkdf2Params;
105
+ });
106
+ export interface CreateKeystoreOptions {
107
+ /**
108
+ * KDF 选择。默认 `'auto'`:能跑 Argon2id 就用它,否则退 PBKDF2
109
+ * (退化只发生在**创建**时;解锁永远按密文里写的 KDF 走)。
110
+ */
111
+ kdf?: KdfAlgorithm | 'auto';
112
+ /** Argon2id 参数覆盖(盐/长度/版本由本包决定,不接受覆盖) */
113
+ argon2?: Partial<Pick<Argon2idParams, 'memory' | 'cost' | 'parallelism'>>;
114
+ /** PBKDF2 参数覆盖(迭代次数不得低于设计下限 60 万) */
115
+ pbkdf2?: {
116
+ iterations?: number;
117
+ };
118
+ /**
119
+ * 口令强度门(0..4)。**默认不设**(本函数是密码学原语,不替调用方做产品策略)。
120
+ *
121
+ * 但 SDK 方案 §3.3.6 要求"评分 < 3 拒绝",所以 PWA 的建户流程应当传
122
+ * `minStrength: 3`;也可以先自己调 `estimatePasswordStrength()` 给用户完整
123
+ * 的实时反馈再调本函数(两条路都行,结果一致)。
124
+ * 传入时按需动态载入口令强度模块(zxcvbn 字典约 1 MB,不建户就不该加载)。
125
+ */
126
+ minStrength?: number;
127
+ /** 强度估算选项(仅当传了 `minStrength` 时生效) */
128
+ strengthOptions?: {
129
+ extraBlocklist?: readonly string[];
130
+ userInputs?: readonly string[];
131
+ };
132
+ }
133
+ /** 明文头的规范化文本(AAD 的输入,格式见文件头;被测试用字面量钉住) */
134
+ export declare function keystoreAadText(header: KeystoreHeader): string;
135
+ /** 明文头的 AAD 字节(= `utf8(keystoreAadText(header))`) */
136
+ export declare function keystoreAad(header: KeystoreHeader): Uint8Array;
137
+ /**
138
+ * 创建密钥库:`secret`(私钥 / BIP39 种子等密钥材料)在 `password` 下加密成密文 blob。
139
+ *
140
+ * **不修改 `secret`**(它是调用方的缓冲区,用完由调用方 `wipe()`);
141
+ * 内部派生的密钥与 KDF 输入字节在返回前清零。
142
+ */
143
+ export declare function createKeystore(secret: Uint8Array, password: string, options?: CreateKeystoreOptions): Promise<KeystoreBlob>;
144
+ /**
145
+ * 解锁密钥库,返回密钥材料明文。
146
+ *
147
+ * 返回的是**新分配的** `Uint8Array`,调用方负责在用完后 `wipe()` 它
148
+ * (本包不缓存明文,也不提供任何持久化明文的路径)。
149
+ */
150
+ export declare function unlockKeystore(keystore: KeystoreBlob | string, password: string): Promise<Uint8Array>;
151
+ /**
152
+ * 校验并归一化一份密文(接受对象或 JSON 字符串)。
153
+ *
154
+ * 这一步是**信任边界**:任何来自磁盘 / IndexedDB / 网络的东西都必须先过这里,
155
+ * 再交给 KDF —— 否则一个 `memory: 2^31` 的 blob 就是一次 DoS。
156
+ */
157
+ export declare function parseKeystore(value: KeystoreBlob | string | unknown): KeystoreBlob;
158
+ /** 只问"能不能当密钥库用",不抛异常 */
159
+ export declare function isKeystoreBlob(value: unknown): value is KeystoreBlob;
160
+ /**
161
+ * 序列化成 JSON 文本(字段顺序固定:version / kdf / kdfParams / cipher / iv / ciphertext / tag)。
162
+ *
163
+ * 固定顺序不是为了好看,而是为了"同一份 keystore 在任何实现里字节一致" ——
164
+ * 备份文件、金标准向量、跨端比对都要靠它。
165
+ */
166
+ export declare function serializeKeystore(blob: KeystoreBlob): string;
167
+ export {};
168
+ //# sourceMappingURL=keystore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keystore.d.ts","sourceRoot":"","sources":["../src/keystore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ /**
3
+ * 加密 keystore(SDK 方案 §3.3.1)—— **只存密文**,口令不落任何地方。
4
+ *
5
+ * ## 密文形状(格式 v1,跨语言可复算)
6
+ *
7
+ * ```jsonc
8
+ * {
9
+ * "version": 1, // 格式版本;不认识就拒,不猜
10
+ * "kdf": "argon2id", // 或 "pbkdf2"
11
+ * "kdfParams": { "salt": "<base64>", "memory": 19456, "cost": 2,
12
+ * "parallelism": 1, "hashLength": 32, "version": 19 },
13
+ * "cipher": "aes-256-gcm",
14
+ * "iv": "<base64, 12 字节>",
15
+ * "ciphertext": "<base64>",
16
+ * "tag": "<base64, 16 字节 GCM 认证标签>"
17
+ * }
18
+ * ```
19
+ *
20
+ * `pbkdf2` 时 `kdfParams` 是 `{salt, iterations, hashLength, hash:'sha256'}`。
21
+ * 字段顺序固定(`serializeKeystore` 显式按序构造),这样同一份 keystore
22
+ * 在任何实现里序列化出来都一样,便于做金标准向量。
23
+ *
24
+ * ## 为什么 KDF 参数要进 AAD(本实现相对设计文档的一点加固)
25
+ *
26
+ * 设计文档列的字段里,`kdf` / `kdfParams` / `version` / `cipher` / `iv` 都是
27
+ * **明文头**:它们不参与解密,但决定了密钥怎么派生。如果只把密文丢给 GCM,
28
+ * 攻击者可以改掉 `kdfParams.cost`(甚至把 `argon2id` 改成 `pbkdf2`)而密文
29
+ * 照样解得开 —— 那就等于"KDF 强度随人调"。所以这里把整个明文头的**规范化
30
+ * 文本**作为 GCM 的 AAD 一起认证:
31
+ *
32
+ * ```
33
+ * trustbase-keystore-v1|kdf=argon2id|salt=<b64>|memory=19456|cost=2|parallelism=1|hashLength=32|version=19|cipher=aes-256-gcm|iv=<b64>
34
+ * trustbase-keystore-v1|kdf=pbkdf2|salt=<b64>|iterations=600000|hashLength=32|hash=sha256|cipher=aes-256-gcm|iv=<b64>
35
+ * ```
36
+ *
37
+ * 任何一位被改 → 认证失败 → `AUTH_FAILED`(与"口令错"同一句话,见 errors.ts)。
38
+ * 这条格式被 `test/keystore.test.ts` 用字面量钉住,改它等于改密钥库格式。
39
+ *
40
+ * ## 内存卫生(说清楚能做到什么)
41
+ *
42
+ * - 派生密钥、KDF 输入字节、解密出的明文:本包都在 `finally` 里 `wipe()`
43
+ * - **不**提供任何把明文写 `localStorage` / `sessionStorage` / IndexedDB 的路径
44
+ * (持久化是调用方的事:设备账户存 IndexedDB,商户侧存本机文件,都只存密文)
45
+ * - 口令是 JS 字符串,**清不掉**(见 bytes.ts `wipe` 的诚实说明);
46
+ * `Uint8Array` 缓冲区我们能清,但引擎/WASM 内部副本清不到
47
+ *
48
+ * ## 校验失败的分类(调用方要能分支)
49
+ *
50
+ * | 情况 | 错误码 |
51
+ * |---|---|
52
+ * | 不是密文 / 字段缺失 / 字段类型不对 / base64 坏 / 参数越界 | `INVALID_BLOB` |
53
+ * | `version` 比本实现新 | `UNSUPPORTED_VERSION` |
54
+ * | `kdf` / `hash` / Argon2 版本不认 | `UNSUPPORTED_KDF` |
55
+ * | 口令错 / 密文被改 / 头被改 | `AUTH_FAILED`(刻意含糊) |
56
+ * | 环境没有 WebCrypto | `CRYPTO_UNAVAILABLE` |
57
+ * | 口令超长(>128)、secret 形状不对 | `INVALID_ARGUMENT` |
58
+ */
59
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
60
+ if (k2 === undefined) k2 = k;
61
+ var desc = Object.getOwnPropertyDescriptor(m, k);
62
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
63
+ desc = { enumerable: true, get: function() { return m[k]; } };
64
+ }
65
+ Object.defineProperty(o, k2, desc);
66
+ }) : (function(o, m, k, k2) {
67
+ if (k2 === undefined) k2 = k;
68
+ o[k2] = m[k];
69
+ }));
70
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
71
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
72
+ }) : function(o, v) {
73
+ o["default"] = v;
74
+ });
75
+ var __importStar = (this && this.__importStar) || (function () {
76
+ var ownKeys = function(o) {
77
+ ownKeys = Object.getOwnPropertyNames || function (o) {
78
+ var ar = [];
79
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
80
+ return ar;
81
+ };
82
+ return ownKeys(o);
83
+ };
84
+ return function (mod) {
85
+ if (mod && mod.__esModule) return mod;
86
+ var result = {};
87
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
88
+ __setModuleDefault(result, mod);
89
+ return result;
90
+ };
91
+ })();
92
+ Object.defineProperty(exports, "__esModule", { value: true });
93
+ exports.MAX_SECRET_MATERIAL_LENGTH = exports.KEYSTORE_AAD_PREFIX = exports.KEYSTORE_CIPHER = exports.KEYSTORE_VERSION = void 0;
94
+ exports.keystoreAadText = keystoreAadText;
95
+ exports.keystoreAad = keystoreAad;
96
+ exports.createKeystore = createKeystore;
97
+ exports.unlockKeystore = unlockKeystore;
98
+ exports.parseKeystore = parseKeystore;
99
+ exports.isKeystoreBlob = isKeystoreBlob;
100
+ exports.serializeKeystore = serializeKeystore;
101
+ const bytes_1 = require("./bytes");
102
+ const errors_1 = require("./errors");
103
+ const kdf_1 = require("./kdf");
104
+ const webcrypto_1 = require("./webcrypto");
105
+ /** 密文格式版本。加字段/换算法都要 bump 它(旧实现会拒收,而不是猜着解) */
106
+ exports.KEYSTORE_VERSION = 1;
107
+ /** 唯一支持的对称加密 */
108
+ exports.KEYSTORE_CIPHER = 'aes-256-gcm';
109
+ /** AAD 前缀(版本化,见文件头) */
110
+ exports.KEYSTORE_AAD_PREFIX = 'trustbase-keystore-v1';
111
+ /** 密钥材料的长度上限(32B 私钥 / 64B BIP39 种子都够;太长的输入说明用错了 API) */
112
+ exports.MAX_SECRET_MATERIAL_LENGTH = 256;
113
+ function assertSecretMaterial(secret) {
114
+ if (!(secret instanceof Uint8Array)) {
115
+ throw new errors_1.AccountError('INVALID_ARGUMENT', '密钥材料必须是 Uint8Array(不要传字符串:字符串无法清零)');
116
+ }
117
+ if (secret.length === 0) {
118
+ throw new errors_1.AccountError('INVALID_ARGUMENT', '密钥材料不能为空');
119
+ }
120
+ if (secret.length > exports.MAX_SECRET_MATERIAL_LENGTH) {
121
+ throw new errors_1.AccountError('INVALID_ARGUMENT', `密钥材料不得超过 ${exports.MAX_SECRET_MATERIAL_LENGTH} 字节(本 API 是密钥库,不是通用文件存储)`);
122
+ }
123
+ }
124
+ /** 明文头的规范化文本(AAD 的输入,格式见文件头;被测试用字面量钉住) */
125
+ function keystoreAadText(header) {
126
+ const parts = [exports.KEYSTORE_AAD_PREFIX, `kdf=${header.kdf}`];
127
+ if (header.kdf === 'argon2id') {
128
+ parts.push(`salt=${header.kdfParams.salt}`, `memory=${header.kdfParams.memory}`, `cost=${header.kdfParams.cost}`, `parallelism=${header.kdfParams.parallelism}`, `hashLength=${header.kdfParams.hashLength}`, `version=${header.kdfParams.version}`);
129
+ }
130
+ else {
131
+ parts.push(`salt=${header.kdfParams.salt}`, `iterations=${header.kdfParams.iterations}`, `hashLength=${header.kdfParams.hashLength}`, `hash=${header.kdfParams.hash}`);
132
+ }
133
+ parts.push(`cipher=${header.cipher}`, `iv=${header.iv}`);
134
+ return parts.join('|');
135
+ }
136
+ /** 明文头的 AAD 字节(= `utf8(keystoreAadText(header))`) */
137
+ function keystoreAad(header) {
138
+ return (0, bytes_1.utf8ToBytes)(keystoreAadText(header));
139
+ }
140
+ function buildKdfSelection(algorithm, options) {
141
+ if (algorithm === 'argon2id') {
142
+ return {
143
+ kdf: 'argon2id',
144
+ kdfParams: {
145
+ salt: (0, kdf_1.generateSalt)(),
146
+ memory: options.argon2?.memory ?? kdf_1.DEFAULT_ARGON2ID_PARAMS.memory,
147
+ cost: options.argon2?.cost ?? kdf_1.DEFAULT_ARGON2ID_PARAMS.cost,
148
+ parallelism: options.argon2?.parallelism ?? kdf_1.DEFAULT_ARGON2ID_PARAMS.parallelism,
149
+ // 长度与版本固定:换它们 = 换格式(要 bump KEYSTORE_VERSION)
150
+ hashLength: kdf_1.DERIVED_KEY_LENGTH,
151
+ version: kdf_1.ARGON2ID_VERSION,
152
+ },
153
+ };
154
+ }
155
+ return {
156
+ kdf: 'pbkdf2',
157
+ kdfParams: {
158
+ salt: (0, kdf_1.generateSalt)(),
159
+ iterations: options.pbkdf2?.iterations ?? kdf_1.DEFAULT_PBKDF2_ITERATIONS,
160
+ hashLength: kdf_1.DERIVED_KEY_LENGTH,
161
+ hash: 'sha256',
162
+ },
163
+ };
164
+ }
165
+ /** 明文头 + 密文/标签 = 完整 blob(显式按判别联合拼装,避免类型走散) */
166
+ function withCiphertext(header, ciphertext, tag) {
167
+ if (header.kdf === 'argon2id') {
168
+ return {
169
+ version: header.version,
170
+ kdf: 'argon2id',
171
+ kdfParams: header.kdfParams,
172
+ cipher: header.cipher,
173
+ iv: header.iv,
174
+ ciphertext,
175
+ tag,
176
+ };
177
+ }
178
+ return {
179
+ version: header.version,
180
+ kdf: 'pbkdf2',
181
+ kdfParams: header.kdfParams,
182
+ cipher: header.cipher,
183
+ iv: header.iv,
184
+ ciphertext,
185
+ tag,
186
+ };
187
+ }
188
+ /**
189
+ * 创建密钥库:`secret`(私钥 / BIP39 种子等密钥材料)在 `password` 下加密成密文 blob。
190
+ *
191
+ * **不修改 `secret`**(它是调用方的缓冲区,用完由调用方 `wipe()`);
192
+ * 内部派生的密钥与 KDF 输入字节在返回前清零。
193
+ */
194
+ async function createKeystore(secret, password, options = {}) {
195
+ assertSecretMaterial(secret);
196
+ if (options.minStrength !== undefined) {
197
+ const { estimatePasswordStrength } = await Promise.resolve().then(() => __importStar(require('./password')));
198
+ const strength = estimatePasswordStrength(password, options.strengthOptions);
199
+ if (strength.score < options.minStrength) {
200
+ throw new errors_1.AccountError('WEAK_PASSWORD', `口令强度不足(评分 ${strength.score}/4,需要 ≥ ${options.minStrength}):${strength.reasons.join(';')}`, { score: strength.score, reasons: strength.reasons, acceptable: strength.acceptable });
201
+ }
202
+ }
203
+ const kdfInput = (0, kdf_1.passwordToKdfInput)(password);
204
+ try {
205
+ const requested = options.kdf ?? 'auto';
206
+ const algorithm = requested === 'auto' ? ((await (0, kdf_1.isArgon2idAvailable)()) ? 'argon2id' : 'pbkdf2') : requested;
207
+ const selection = buildKdfSelection(algorithm, options);
208
+ (0, kdf_1.assertCreateTimeKdfParams)(selection.kdf, selection.kdfParams);
209
+ const key = selection.kdf === 'argon2id'
210
+ ? await (0, kdf_1.deriveArgon2idKey)(selection.kdfParams, kdfInput)
211
+ : await (0, kdf_1.derivePbkdf2Key)(selection.kdfParams, kdfInput);
212
+ try {
213
+ const iv = (0, bytes_1.base64Encode)((0, webcrypto_1.randomBytes)(webcrypto_1.AES_GCM_IV_LENGTH));
214
+ const header = selection.kdf === 'argon2id'
215
+ ? { version: exports.KEYSTORE_VERSION, kdf: 'argon2id', kdfParams: selection.kdfParams, cipher: exports.KEYSTORE_CIPHER, iv }
216
+ : { version: exports.KEYSTORE_VERSION, kdf: 'pbkdf2', kdfParams: selection.kdfParams, cipher: exports.KEYSTORE_CIPHER, iv };
217
+ const sealed = await (0, webcrypto_1.aesGcmEncrypt)(key, (0, bytes_1.base64Decode)(iv), secret, keystoreAad(header));
218
+ return withCiphertext(header, (0, bytes_1.base64Encode)(sealed.ciphertext), (0, bytes_1.base64Encode)(sealed.tag));
219
+ }
220
+ finally {
221
+ (0, bytes_1.wipe)(key);
222
+ }
223
+ }
224
+ finally {
225
+ // 口令的 UTF-8 副本是我们自己的缓冲区 —— 这是"能清的那一半"
226
+ (0, bytes_1.wipe)(kdfInput);
227
+ }
228
+ }
229
+ /**
230
+ * 解锁密钥库,返回密钥材料明文。
231
+ *
232
+ * 返回的是**新分配的** `Uint8Array`,调用方负责在用完后 `wipe()` 它
233
+ * (本包不缓存明文,也不提供任何持久化明文的路径)。
234
+ */
235
+ async function unlockKeystore(keystore, password) {
236
+ const blob = parseKeystore(keystore);
237
+ const kdfInput = (0, kdf_1.passwordToKdfInput)(password);
238
+ try {
239
+ const key = blob.kdf === 'argon2id'
240
+ ? await (0, kdf_1.deriveArgon2idKey)(blob.kdfParams, kdfInput)
241
+ : await (0, kdf_1.derivePbkdf2Key)(blob.kdfParams, kdfInput);
242
+ try {
243
+ return await (0, webcrypto_1.aesGcmDecrypt)(key, (0, bytes_1.decodeBlobField)(blob.iv, 'iv', { exact: webcrypto_1.AES_GCM_IV_LENGTH }), (0, bytes_1.decodeBlobField)(blob.ciphertext, 'ciphertext', { min: 1 }), (0, bytes_1.decodeBlobField)(blob.tag, 'tag', { exact: webcrypto_1.AES_GCM_TAG_LENGTH }), keystoreAad(blob));
244
+ }
245
+ finally {
246
+ (0, bytes_1.wipe)(key);
247
+ }
248
+ }
249
+ finally {
250
+ (0, bytes_1.wipe)(kdfInput);
251
+ }
252
+ }
253
+ const COMMON_KEYS = ['version', 'kdf', 'kdfParams', 'cipher', 'iv', 'ciphertext', 'tag'];
254
+ function assertExactKeys(raw) {
255
+ for (const key of COMMON_KEYS) {
256
+ if (!(key in raw)) {
257
+ throw new errors_1.AccountError('INVALID_BLOB', `密钥库缺少字段:${key}`);
258
+ }
259
+ }
260
+ for (const key of Object.keys(raw)) {
261
+ if (!COMMON_KEYS.includes(key)) {
262
+ // v1 的形状是冻结的:多出来的字段意味着这不是我们的密文(或有人往里塞数据)
263
+ throw new errors_1.AccountError('INVALID_BLOB', `密钥库含未知字段:${key}`);
264
+ }
265
+ }
266
+ }
267
+ /**
268
+ * 校验并归一化一份密文(接受对象或 JSON 字符串)。
269
+ *
270
+ * 这一步是**信任边界**:任何来自磁盘 / IndexedDB / 网络的东西都必须先过这里,
271
+ * 再交给 KDF —— 否则一个 `memory: 2^31` 的 blob 就是一次 DoS。
272
+ */
273
+ function parseKeystore(value) {
274
+ let raw = value;
275
+ if (typeof value === 'string') {
276
+ try {
277
+ raw = JSON.parse(value);
278
+ }
279
+ catch {
280
+ throw new errors_1.AccountError('INVALID_BLOB', '密钥库不是合法 JSON');
281
+ }
282
+ }
283
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
284
+ throw new errors_1.AccountError('INVALID_BLOB', '密钥库必须是对象');
285
+ }
286
+ const record = raw;
287
+ if (record.version !== exports.KEYSTORE_VERSION) {
288
+ // 只有"整数且比本实现新"才算版本问题(提示升级);1.5 / "1" / null 一律是结构非法
289
+ if (typeof record.version === 'number' && Number.isInteger(record.version) && record.version > exports.KEYSTORE_VERSION) {
290
+ throw new errors_1.AccountError('UNSUPPORTED_VERSION', `这份密钥库的格式版本是 ${record.version},本实现只认识 ${exports.KEYSTORE_VERSION} —— 请升级客户端后再打开`);
291
+ }
292
+ throw new errors_1.AccountError('INVALID_BLOB', `密钥库的 version 非法:${String(record.version)}`);
293
+ }
294
+ if (record.cipher !== exports.KEYSTORE_CIPHER) {
295
+ throw new errors_1.AccountError('INVALID_BLOB', `密钥库的 cipher 非法:${String(record.cipher)}`);
296
+ }
297
+ if (!(0, kdf_1.isKdfAlgorithm)(record.kdf)) {
298
+ throw new errors_1.AccountError('UNSUPPORTED_KDF', `密钥库声明的 kdf 本实现不支持:${String(record.kdf)}`);
299
+ }
300
+ assertExactKeys(record);
301
+ if (record.kdf === 'argon2id') {
302
+ (0, kdf_1.assertArgon2idParams)(record.kdfParams);
303
+ }
304
+ else {
305
+ (0, kdf_1.assertPbkdf2Params)(record.kdfParams);
306
+ }
307
+ (0, bytes_1.decodeBlobField)(record.iv, 'iv', { exact: webcrypto_1.AES_GCM_IV_LENGTH });
308
+ (0, bytes_1.decodeBlobField)(record.tag, 'tag', { exact: webcrypto_1.AES_GCM_TAG_LENGTH });
309
+ (0, bytes_1.decodeBlobField)(record.ciphertext, 'ciphertext', { min: 1 });
310
+ return record;
311
+ }
312
+ /** 只问"能不能当密钥库用",不抛异常 */
313
+ function isKeystoreBlob(value) {
314
+ try {
315
+ parseKeystore(value);
316
+ return true;
317
+ }
318
+ catch {
319
+ return false;
320
+ }
321
+ }
322
+ /**
323
+ * 序列化成 JSON 文本(字段顺序固定:version / kdf / kdfParams / cipher / iv / ciphertext / tag)。
324
+ *
325
+ * 固定顺序不是为了好看,而是为了"同一份 keystore 在任何实现里字节一致" ——
326
+ * 备份文件、金标准向量、跨端比对都要靠它。
327
+ */
328
+ function serializeKeystore(blob) {
329
+ const normalized = parseKeystore(blob);
330
+ return JSON.stringify({
331
+ version: normalized.version,
332
+ kdf: normalized.kdf,
333
+ kdfParams: normalized.kdfParams,
334
+ cipher: normalized.cipher,
335
+ iv: normalized.iv,
336
+ ciphertext: normalized.ciphertext,
337
+ tag: normalized.tag,
338
+ });
339
+ }
340
+ //# sourceMappingURL=keystore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keystore.js","sourceRoot":"","sources":["../src/keystore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * BIP39 助记词(纸密钥)与"抄写门"。
3
+ *
4
+ * ## 定位(SDK 方案 §3.3.3 第 1 条,必须写进产品文案)
5
+ *
6
+ * **助记词是唯一的"忘记密码"退路。** 我们不掌握任何解密材料,没有找回密码
7
+ * 服务,也没有云端解密备份 —— 这不是能力不足,而是设计:一旦我们能解密,
8
+ * 我们就成了资金保管方。所以"创建账户必须真的抄一遍助记词"(不是打个勾),
9
+ * 本文件提供抄写门需要的两个原语。
10
+ *
11
+ * ## 生成严出、校验宽进(有意的不对称)
12
+ *
13
+ * | 方向 | 口径 | 理由 |
14
+ * |---|---|---|
15
+ * | 生成 | 只允许 **128 / 256 bit**(12 / 24 词) | 我们只造这两种,少一种长度就少一类"用户抄错还没发现"的可能 |
16
+ * | 校验 | 接受任何合法 BIP39(12..24 词、词表内、校验和对) | 用户拿来的旧纸密钥可能是 15/18/21 词的。这里是**用户的钱**,不因为我们不喜欢这个长度就拒收 |
17
+ *
18
+ * 助记词派生(BIP39 种子 → BIP32 → secp256k1 → 地址)在 `hd.ts`。
19
+ */
20
+ /** 我们**生成**的助记词强度(bit) */
21
+ export declare const MNEMONIC_STRENGTHS: readonly [128, 256];
22
+ export type MnemonicStrength = (typeof MNEMONIC_STRENGTHS)[number];
23
+ /** 默认强度:128 bit(12 词)—— 手机上手抄得完 */
24
+ export declare const DEFAULT_MNEMONIC_STRENGTH: MnemonicStrength;
25
+ /** 随机源签名(与 `crypto.getRandomValues` 同形;测试可注入确定性替身) */
26
+ export type RandomSource = (length: number) => Uint8Array;
27
+ /** 生成新助记词(默认 12 词)。随机源只用 `crypto.getRandomValues`(见 webcrypto.ts)。 */
28
+ export declare function generateMnemonic(strength?: MnemonicStrength, rng?: RandomSource): string;
29
+ /**
30
+ * 归一化助记词文本:去掉首尾空白、把**任意空白**(多空格/换行/制表符)折成
31
+ * 单个空格、再做 NFKD 归一(BIP39 规定)。
32
+ *
33
+ * 为什么必须做:BIP39 的种子是 `PBKDF2(助记词字符串)` —— 多一个空格就是
34
+ * **另一份种子、另一个账户**。用户从手机备忘录/邮件里粘贴时几乎一定带多余空白,
35
+ * 于是会出现"抄对了却打不开"这种最伤人的失败。归一化把这个失败模式从
36
+ * "用户的问题"变成"我们处理掉的问题"。
37
+ *
38
+ * 空白以外的字符**不**做宽容处理(不做大小写折叠、不做同义词):那是真的抄错了。
39
+ *
40
+ * @throws AccountError 输入为空(没有任何词)时
41
+ */
42
+ export declare function normalizeMnemonic(mnemonic: string): string;
43
+ /**
44
+ * 是不是合法助记词(词表 + 校验和 + 词数)。
45
+ * 宽进:12/15/18/21/24 词都接受(见文件头);空白先归一化(见 `normalizeMnemonic`)。
46
+ */
47
+ export declare function validateMnemonic(mnemonic: string): boolean;
48
+ /** 助记词 → 熵(字节)。非法助记词抛 `INVALID_MNEMONIC` */
49
+ export declare function mnemonicToEntropy(mnemonic: string): Uint8Array;
50
+ /** 熵 → 助记词。只接受 16 字节(128 bit)与 32 字节(256 bit) */
51
+ export declare function entropyToMnemonic(entropy: Uint8Array): string;
52
+ /** 把助记词切成词数组(按任意空白切;不做大小写/字符归一 —— 那是抄错,不是格式问题) */
53
+ export declare function splitMnemonicWords(mnemonic: string): string[];
54
+ /**
55
+ * 抄写门:从助记词里随机抽 `count` 个位置,让用户回填。
56
+ *
57
+ * 返回**升序去重的 0 基下标**(数组下标语义,`words[i]` 直接可用)。
58
+ * UI 要显示"第几个词"时用 `index + 1`(人类从 1 数)—— 这个转换留在调用方,
59
+ * 因为它是文案问题,不是密码学问题。
60
+ */
61
+ export declare function pickTranscriptionIndices(input: string | readonly string[], count: number, rng?: RandomSource): number[];
62
+ export interface TranscriptionAnswer {
63
+ /** 0 基下标(`pickTranscriptionIndices` 给的那套) */
64
+ index: number;
65
+ /** 用户填回来的词 */
66
+ word: string;
67
+ }
68
+ export interface TranscriptionResult {
69
+ /** 全部填对(`failed` 与 `missing` 都空)才算过 */
70
+ ok: boolean;
71
+ /** 填了但填错的位置(0 基,升序) */
72
+ failed: number[];
73
+ /** 根本没填的位置(0 基,升序) */
74
+ missing: number[];
75
+ }
76
+ /**
77
+ * 校验抄写结果。
78
+ *
79
+ * `answers` 既接受 `{index, word}[]`,也接受 `{ [index]: word }`(表单更顺手)。
80
+ * **不返回正确答案**(`failed`/`missing` 只给位置)—— 否则这个 API 就成了
81
+ * 一个"帮你把助记词填回去"的工具,抄写门也就白设了。
82
+ */
83
+ export declare function checkTranscription(input: string | readonly string[], indices: readonly number[], answers: readonly TranscriptionAnswer[] | Readonly<Record<number, string>>): TranscriptionResult;
84
+ //# sourceMappingURL=mnemonic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mnemonic.d.ts","sourceRoot":"","sources":["../src/mnemonic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,0BAA0B;AAC1B,eAAO,MAAM,kBAAkB,qBAAsB,CAAC;AACtD,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,mCAAmC;AACnC,eAAO,MAAM,yBAAyB,EAAE,gBAAsB,CAAC;AAE/D,sDAAsD;AACtD,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,UAAU,CAAC;AAE1D,sEAAsE;AACtE,wBAAgB,gBAAgB,CAC9B,QAAQ,GAAE,gBAA4C,EACtD,GAAG,GAAE,YAA0B,GAC9B,MAAM,CAWR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAO1D;AAED,4CAA4C;AAC5C,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAQ9D;AAED,iDAAiD;AACjD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAK7D;AAED,kDAAkD;AAClD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAY7D;AAuBD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACjC,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,YAA0B,GAC9B,MAAM,EAAE,CAUV;AAED,MAAM,WAAW,mBAAmB;IAClC,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,cAAc;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,uCAAuC;IACvC,EAAE,EAAE,OAAO,CAAC;IACZ,uBAAuB;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sBAAsB;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAOD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACjC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,SAAS,mBAAmB,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACzE,mBAAmB,CAoCrB"}