@tacet-ink/journal-core 0.3.2 → 0.3.4
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 +136 -36
- package/dist/client/bip39.d.ts +33 -0
- package/dist/client/bip39.js +12 -0
- package/dist/client/vault-error.d.ts +4 -1
- package/dist/client/vault-error.js +9 -2
- package/dist/client/vault.d.ts +94 -15
- package/dist/client/vault.js +155 -27
- package/dist/server/ratelimit.d.ts +1 -0
- package/dist/server/ratelimit.js +1 -0
- package/package.json +1 -6
- package/scripts/prepare-core-pkg.cjs +0 -74
- package/scripts/verify-core-crypto.ts +0 -3377
- package/src/client/argon-auto.ts +0 -224
- package/src/client/argon-worker.ts +0 -147
- package/src/client/argon2.ts +0 -532
- package/src/client/bip39.ts +0 -145
- package/src/client/keys.ts +0 -27
- package/src/client/note-crypto.ts +0 -719
- package/src/client/pinlock.ts +0 -110
- package/src/client/vault-error.ts +0 -51
- package/src/client/vault.ts +0 -486
- package/src/client/vendor/hash-wasm-argon2.ts +0 -27
- package/src/client/wordlist.ts +0 -264
- package/src/index.ts +0 -104
- package/src/server/auth.ts +0 -225
- package/src/server/cors.ts +0 -22
- package/src/server/env.ts +0 -13
- package/src/server/hash.ts +0 -28
- package/src/server/ladder.ts +0 -76
- package/src/server/ratelimit.ts +0 -123
package/README.md
CHANGED
|
@@ -10,43 +10,59 @@
|
|
|
10
10
|
|
|
11
11
|
> **English**: Zero-knowledge journaling core — E2E-encrypted notes, passphrase-as-identity auth, transport & rate-limiting primitives. The server never sees a single word: the passphrase never crosses the wire, and all key wrapping and encryption happen client-side. See the [English summary](#english-summary) below.
|
|
12
12
|
|
|
13
|
+
高階 Vault API(`client/vault`):`createVault(pass, { app })` 零設定建 vault → 記錄包裹加解密 → 解鎖/24 詞復原 → PH1 登入憑證,一個子路徑走完。
|
|
14
|
+
|
|
13
15
|
這是 [默·Tacet(tacet.ink)](https://tacet.ink) 的客戶端/伺服端核心原語層,
|
|
14
16
|
從多個姊妹產品共用的密碼學本體抽取而成,以品牌前綴參數化(前綴契約見下表)。
|
|
15
17
|
|
|
16
18
|
## Quickstart
|
|
17
19
|
|
|
18
|
-
```sh
|
|
19
|
-
git clone https://github.com/tacet-ink/journal-core.git && cd journal-core
|
|
20
|
-
npm ci --ignore-scripts
|
|
21
|
-
npm run verify # 521 assertions, all green, run against the real modules (no mocks)
|
|
22
|
-
```
|
|
23
|
-
|
|
24
20
|
```ts
|
|
25
|
-
// example.
|
|
26
|
-
import {
|
|
27
|
-
|
|
21
|
+
// example.mts
|
|
22
|
+
import { createVault, unlockVault, recoverVault, deriveVaultPh1, isVaultError }
|
|
23
|
+
from '@tacet-ink/journal-core/client/vault';
|
|
24
|
+
|
|
25
|
+
const PASS = 'correct horse battery staple';
|
|
26
|
+
const cfg = { app: 'myapp', recovery: true }; // 零設定:全前綴由 app 推導(myapp1v./myapp4w./myapp1r.…);recovery=復原套件 opt-in
|
|
27
|
+
|
|
28
|
+
// 1. 建 vault:隨機 noteKey+Argon2id 密語包裹。serverRecord=上傳伺服器的包裹欄;kit=復原套件(只顯示一次)
|
|
29
|
+
const { vault, serverRecord, recovery: kit } = await createVault(PASS, cfg);
|
|
30
|
+
|
|
31
|
+
// 2. 記錄加解密:AAD 綁 app+recordId——密文搬到別筆 recordId 即 ERR_VAULT_DECRYPT
|
|
32
|
+
const blob = await vault.encrypt('note-1', { title: '今天', body: '寫了一點東西。' });
|
|
33
|
+
console.log(blob.slice(0, 8)); // 'myapp1v.'
|
|
34
|
+
console.log(await vault.decrypt('note-1', blob)); // { title: '今天', body: '寫了一點東西。' }
|
|
35
|
+
|
|
36
|
+
// 3. 換裝置解鎖:密語+伺服器帶回的 serverRecord(同一 cfg)
|
|
37
|
+
const unlocked = await unlockVault(PASS, serverRecord, cfg);
|
|
38
|
+
console.log(await unlocked.decrypt('note-1', blob)); // 同上
|
|
39
|
+
try { await unlockVault('wrong passphrase', serverRecord, cfg); }
|
|
40
|
+
catch (e) { console.log(isVaultError(e, 'ERR_VAULT_WRONG_PASSPHRASE')); } // true(拋出碼恆 ERR_VAULT_WRAP_MISMATCH)
|
|
41
|
+
|
|
42
|
+
// 4. 忘記密語:24 詞復原(伺服器存 kit.wrappedRec+kit.recTokenHash;詞只給使用者抄寫)
|
|
43
|
+
if (!kit) throw new Error('recovery kit missing'); // cfg 未開 recovery=null
|
|
44
|
+
console.log(kit.words.length); // 24
|
|
45
|
+
const recovered = await recoverVault(kit.words, kit.wrappedRec, cfg);
|
|
46
|
+
console.log(await recovered.decrypt('note-1', blob)); // 同上
|
|
47
|
+
const newRecord = await recovered.changePassphrase('a new passphrase'); // 設新密語 → 覆存 serverRecord
|
|
48
|
+
|
|
49
|
+
// 5. 登入憑證:PH1(hex64)上線、密語本體不過線;鹽=per-product 固定域鹽(⚠️ 見下方「PH1 派生」節鹽域警告)
|
|
50
|
+
const ph1 = await deriveVaultPh1(PASS, new TextEncoder().encode('myapp-ph1-v1'));
|
|
51
|
+
console.log(ph1.length); // 64
|
|
52
|
+
// server(fork 端,'@tacet-ink/journal-core/server/auth'):return loginRouteCore(env, store, ip, ph1);
|
|
53
|
+
```
|
|
28
54
|
|
|
29
|
-
|
|
30
|
-
guestKdfPrefix: 'myapp-note-u1',
|
|
31
|
-
recSaltPrefix: 'myapp-note-rec1:',
|
|
32
|
-
cipherGuest: 'jr1g.', // your own brand prefix family (guest era, opt-in)
|
|
33
|
-
cipherBound: 'jr1b.',
|
|
34
|
-
wrap: 'jr1w.',
|
|
35
|
-
store: makeKeyStore({ brand: 'myapp' }),
|
|
36
|
-
};
|
|
55
|
+
run: `npm install @tacet-ink/journal-core && node example.mts`(0.3.4 起;Node ≥ 24.7——dist ESM 直載免 strip-types 旗標,`.mts` 不受 package.json `type` 影響;Argon2id 走內建 `node:crypto` 免注入 loader)
|
|
37
56
|
|
|
38
|
-
|
|
39
|
-
held.set(await generateNoteKey()); // bound era: random 256-bit noteKey
|
|
40
|
-
const identity = { current: () => 'acct-xxxxxxxxxxxxxxxx' };
|
|
57
|
+
源碼 clone 跑驗證閘:
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
59
|
+
```sh
|
|
60
|
+
git clone https://github.com/tacet-ink/journal-core.git && cd journal-core
|
|
61
|
+
npm ci --ignore-scripts
|
|
62
|
+
npm run verify # 531 assertions, all green, run against the real modules (no mocks); needs this repo checkout — the published package is dist-only since 0.3.4 (no gate, no src/, no scripts/)
|
|
45
63
|
```
|
|
46
64
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
完整 API 見[使用(Usage)](#使用usage)節。Live product: [https://tacet.ink](https://tacet.ink)
|
|
65
|
+
上例為高階 Vault API;錯誤碼見[例外契約](#例外契約錯誤碼語意)(`VaultError` 表)。低階原語範例(note-crypto 兩時代加解密、前綴自配)與完整 API 見[使用(Usage)](#使用usage)節。Live product: [https://tacet.ink](https://tacet.ink)
|
|
50
66
|
(驗證頁:[https://tacet.ink/verify](https://tacet.ink/verify))。
|
|
51
67
|
安全回報流程見 [SECURITY.md](./SECURITY.md)、版本歷史見 [CHANGELOG.md](./CHANGELOG.md)。
|
|
52
68
|
|
|
@@ -80,8 +96,10 @@ run: `node --experimental-strip-types example.ts`
|
|
|
80
96
|
| `src/server/ratelimit.ts` | per-IP fixed-window 限流(單句 UPSERT…RETURNING;D1 計數,跨 isolate 有效;IPv6 鍵經 `normalizeRateIp` 聚合至 /64;IPv4-mapped 位址回點分 IPv4 鍵(=原生同 /32 鍵)) |
|
|
81
97
|
| `src/server/cors.ts`/`hash.ts` | 共用 CORS/雜湊工具(`src/server/env.ts` 為內部 Env 介面,不入 exports) |
|
|
82
98
|
|
|
83
|
-
發行形(0.3.0 起):`dist/`=tsc 原生 emit 的 ESM `.js`+`.d.ts`,`exports` 全部子路徑指向 dist
|
|
84
|
-
`
|
|
99
|
+
發行形(0.3.0 起):`dist/`=tsc 原生 emit 的 ESM `.js`+`.d.ts`,`exports` 全部子路徑指向 dist。
|
|
100
|
+
0.3.4 起發行包 dist-only:`files` 白名單恰 `dist`/`docs`/`migrations`/README/LICENSE——`src/`、`scripts/`
|
|
101
|
+
不再隨包(驗證閘與向量產生器為 repo 樹面,走源碼 clone 動線);稽核/閱讀路徑=GitHub 瀏覽 `src/`+
|
|
102
|
+
[docs/format-spec.md](./docs/format-spec.md) 規格+[docs/vectors/](./docs/vectors/) 向量(`docs/` 仍隨包)。
|
|
85
103
|
|
|
86
104
|
## 前綴契約(家族表:13 個資料前綴+閘對照組 jr1g.)
|
|
87
105
|
|
|
@@ -124,7 +142,7 @@ jr3d/jr4d 兩入參數同輸入,域分離由 info 承載;凍結 KAT 兩 blob
|
|
|
124
142
|
|
|
125
143
|
```sh
|
|
126
144
|
npm ci --ignore-scripts
|
|
127
|
-
npm run verify #
|
|
145
|
+
npm run verify # 531 斷言對真模組(禁鏡像;限流單句 UPSERT…RETURNING 直載真 SQLite):roundtrip/AAD 防搬移/
|
|
128
146
|
# extractable/時代隔離/跨前綴家族隔離/payload 竄改/RFC 9106 KAT/BIP39 @scure 對照 200 組/
|
|
129
147
|
# server-side(inboundCipher/validWrappedKey/pickKeyPackage/checkRate/timingSafeEq)+ pinlock 全函式
|
|
130
148
|
# + 密語正規化 v3 世代(normalizePassphrase/jr4w./jr4d./PH1 v3 鹽域分離+帶內版本化舊契約向量)
|
|
@@ -136,7 +154,7 @@ npm run verify # 521 斷言對真模組(禁鏡像;限流單句 UPSERT…RE
|
|
|
136
154
|
# +KAT 凍結向量 [17](HKDF info 域世代分離+v3/raw 入口契約四 blob)
|
|
137
155
|
# +包裹前綴快檢 [13-2](錯前綴試探免付 KDF:五入口行為+計時帽+計數錨)
|
|
138
156
|
# +CI 供應鏈面 [21](npm ci --ignore-scripts+dependabot runner fromJSON 分流+腳本摘除+NIT-1 錨+r2 MINOR-1 URL 直傳;
|
|
139
|
-
#
|
|
157
|
+
# 531 帳=repo 樹——workflow 錨在 .github=repo 樹恆在、四條全跑;包內 SKIP 對照帳段隨 0.3.4 dist-only 瘦身退役:發行包不再帶閘=無包內帳可言)
|
|
140
158
|
# +發行 tarball 治理面 [22](pack 白名單零 open-card-pr.sh)
|
|
141
159
|
# +格式規格+測試向量 [26](docs/format-spec.md+docs/vectors 存在性/位元組 shape/欄位名對規格/凍結 KAT 逐字對閘)
|
|
142
160
|
# +高階 Vault API [27](roundtrip 三形/新舊前綴解鎖/upgrade/換密語/復原/VaultError 毒化/載體覆寫+Worker 回退鏈/vendor 孿生)
|
|
@@ -162,6 +180,35 @@ import { makeKeyStore, makeHeldKey, generateNoteKey, encryptNote, decryptNote }
|
|
|
162
180
|
// 或子路徑:'@tacet-ink/journal-core/client/note-crypto'
|
|
163
181
|
```
|
|
164
182
|
|
|
183
|
+
低階原語最小 roundtrip(源碼 clone 形,import `./src/*`):
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// example.ts
|
|
187
|
+
import { makeHeldKey, generateNoteKey, encryptNote, decryptNote } from './src/index.ts';
|
|
188
|
+
import { makeKeyStore } from './src/client/keys.ts';
|
|
189
|
+
|
|
190
|
+
const cfg = {
|
|
191
|
+
guestKdfPrefix: 'myapp-note-u1',
|
|
192
|
+
recSaltPrefix: 'myapp-note-rec1:',
|
|
193
|
+
cipherGuest: 'jr1g.', // your own brand prefix family (guest era, opt-in)
|
|
194
|
+
cipherBound: 'jr1b.',
|
|
195
|
+
wrap: 'jr1w.',
|
|
196
|
+
store: makeKeyStore({ brand: 'myapp' }),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const held = makeHeldKey();
|
|
200
|
+
held.set(await generateNoteKey()); // bound era: random 256-bit noteKey
|
|
201
|
+
const identity = { current: () => 'acct-xxxxxxxxxxxxxxxx' };
|
|
202
|
+
|
|
203
|
+
const cipher = await encryptNote(cfg, held, '今天寫了一點東西。', 'noteId:n1', identity);
|
|
204
|
+
console.log(cipher.slice(0, 5)); // 'jr1b.' — ciphertext; nobody reads it without the key
|
|
205
|
+
console.log(await decryptNote(cfg, held, cipher, 'noteId:n1', identity)); // '今天寫了一點東西。' — roundtrip
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
run: `node --experimental-strip-types example.ts`
|
|
209
|
+
|
|
210
|
+
npm 安裝形(完整 cfg 欄位):
|
|
211
|
+
|
|
165
212
|
```ts
|
|
166
213
|
import { generateNoteKey, encryptNote, decryptNote, makeHeldKey,
|
|
167
214
|
wrapNoteKey, unwrapNoteKey } from '@tacet-ink/journal-core/client/note-crypto';
|
|
@@ -199,7 +246,7 @@ raw 'PASS' 撞同一 ph2)。派生輸入吃 `normalizePassphrase`(NFKC-only
|
|
|
199
246
|
大小寫差恆不同 ph2);v2/legacy 派生面零變更(舊帳戶憑證 raw 契約)。兩代 ph2 可並存查表=
|
|
200
247
|
tacet 遷移層職責(v0.2.0 世代收口)。
|
|
201
248
|
|
|
202
|
-
server 登入動線(fork 端):`loginRouteCore(
|
|
249
|
+
server 登入動線(fork 端):`loginRouteCore(env, store, ip, ph1)` 內建
|
|
203
250
|
ladder 查表守衛——現值 miss 時經 `AuthStore.ladderLookup`(optional)查 ph2_ladder,
|
|
204
251
|
命中回舊帳不建幽靈、兩面 miss 才 `createUser`;未配置=行為與 0.2.3 單查恆等。
|
|
205
252
|
接法:store 實作 `ladderLookup`(tacet 參考形=`SQL_FIND_PH2_LADDER_BY_OLD`+fail-open
|
|
@@ -209,6 +256,20 @@ catch)+套用 `migrations/0011-ph2-ladder.sql`(+0012 索引修正);
|
|
|
209
256
|
把 v3 鹽域值餵 v2 派生(或 fork 把同一 saltArg 帶到兩面)=NFKC-effective 密語與 v2 raw 密語
|
|
210
257
|
同值撞 ph2,跨世代帳戶空間混合。v3 的 `saltArg` 必須異於本 product 的 v2 鹽域值。
|
|
211
258
|
|
|
259
|
+
高階 Vault API 零設定前綴(`resolveVaultConfig`,0.3.4):`createVault(pass, { app })` 最小設定
|
|
260
|
+
——全前綴由 app 決定性推導:記錄密文 `<app>1v.`、寫面 `<app>4w.`/PIN `<app>4d.`、
|
|
261
|
+
鹽域 `<app>3s:`、舊世代讀面 `<app>3w.`/`<app>3d.`/`<app>1w.`/`<app>2w.`/`<app>1s.`;
|
|
262
|
+
`recovery: true` opt-in(復原套件 `<app>1r.`+HKDF info 域 `<app>kek1`,recKekSalt 未指定=
|
|
263
|
+
零鹽契約照舊)——**未指定恆不推導**(零鹽選擇不可靜默)。app 契約 `^[A-Za-z0-9_-]+$`
|
|
264
|
+
(前綴字面=線上 ASCII 契約)=**零設定路徑專載**——非契約 app 誤入零設定=
|
|
265
|
+
`ERR_VAULT_CONFIG` fail-closed 不猜;**顯式全配置路徑 app 字面照 0.3.3 既有契約零驗形**
|
|
266
|
+
(非 ASCII/帶點 app 走顯式全配置照舊——契約閘不跨路徑加寬、顯式回歸 diff 恆 0)。
|
|
267
|
+
**顯式路徑判別**:`argon` 或 `cipher` 任一在場=顯式全配置路徑(resolver 零推導——既有
|
|
268
|
+
五值手填契約照舊、顯式配置回歸 diff 恆 0);半顯式態(argon 組推導+`legacy` 物件槽顯式欄位)
|
|
269
|
+
推導值與顯式值同進前綴互斥檢查(例 `legacy: { wrap: <app>4w.bait }` 與推導 `<app>4w.` 互為
|
|
270
|
+
前綴=拒——歧義配置不猜)。跨設定形前綴隔離照舊(推導包裹顯式設定不解、顯式包裹他設定不解
|
|
271
|
+
=`ERR_VAULT_WRAP_UNKNOWN`,app 即前綴契約域)。
|
|
272
|
+
|
|
212
273
|
### 例外契約(錯誤碼語意)
|
|
213
274
|
|
|
214
275
|
**客戶端(client/)**
|
|
@@ -236,6 +297,31 @@ catch)+套用 `migrations/0011-ph2-ladder.sql`(+0012 索引修正);
|
|
|
236
297
|
| `ERR_ARGON2_TAGLEN` | Argon2id 載體 tag 非 32B(載體契約面) |
|
|
237
298
|
| `ERR_ARGON2_UNAVAILABLE` | 無 Argon2id 載體(node<24.7 且無 hash-wasm 注入——禁 fallback) |
|
|
238
299
|
|
|
300
|
+
**高階 Vault API(client/vault)**
|
|
301
|
+
|
|
302
|
+
低階原語面不動(throw 'ERR_*' 字串錯誤+unwrap 恆 null 的混態照舊)——只在 vault.ts/argon-auto.ts 邊界轉譯:高階 API 一律 throw VaultError,呼叫端以 `code` 分流,不比對 message。
|
|
303
|
+
失敗面刻意不細分「密語錯」與「密文竄改」(同一 GCM 失敗——細分=oracle)。
|
|
304
|
+
|
|
305
|
+
`VaultError`(`e.code`)+型別守衛 `isVaultError(e, code?)`:14 個拋出碼+1 個查詢別名。
|
|
306
|
+
|
|
307
|
+
| 錯誤碼 | 拋出面 |
|
|
308
|
+
| --- | --- |
|
|
309
|
+
| `ERR_VAULT_CONFIG` | 配置缺欄/畸形(app、cipher、argon.wrap4、recovery 兩欄一體…)——opt-in 未配置即拒 |
|
|
310
|
+
| `ERR_VAULT_BAD_RECORD` | serverRecord 缺 wrapped/salt 欄或形不符(不猜) |
|
|
311
|
+
| `ERR_VAULT_WRAP_UNKNOWN` | wrapped 前綴不屬任何已配置家族(未知世代/他產品前綴) |
|
|
312
|
+
| `ERR_VAULT_WRAP_MISMATCH` | 家族辨識成功但解包失敗:密語/PIN 錯或包裹竄改(刻意不分) |
|
|
313
|
+
| `ERR_VAULT_WRONG_PASSPHRASE` | 查詢別名(拋出面恆 WRAP_MISMATCH):`isVaultError(e, 'ERR_VAULT_WRONG_PASSPHRASE')` 與查 `ERR_VAULT_WRAP_MISMATCH` 雙向等值;本 API 不拋此碼 |
|
|
314
|
+
| `ERR_VAULT_PIN_REQUIRED` | 雙因子家族(jr2w./jr3d./jr4d. 形)缺 PIN |
|
|
315
|
+
| `ERR_VAULT_BAD_PASSPHRASE` | 密語空值/非字串 |
|
|
316
|
+
| `ERR_VAULT_BAD_PIN` | PIN 正規化後為空 |
|
|
317
|
+
| `ERR_VAULT_KDF_UNSUPPORTED` | Argon2id 載體缺席或失效(zero-fallback:禁 PBKDF2/zeros 兜底) |
|
|
318
|
+
| `ERR_VAULT_BAD_RECORD_ID` | recordId 非字串或空 |
|
|
319
|
+
| `ERR_VAULT_BAD_DATA` | encrypt 資料形不支援(undefined、BigInt、循環 JSON、非 Uint8Array 的 ArrayBuffer 族…) |
|
|
320
|
+
| `ERR_VAULT_BAD_BLOB` | 密文 blob 形不符(前綴/base64/長度/型別標記) |
|
|
321
|
+
| `ERR_VAULT_DECRYPT` | 密文解密失敗:recordId(AAD)不符、金鑰不符或密文竄改(刻意不分) |
|
|
322
|
+
| `ERR_VAULT_RECOVERY` | 復原套件形不符(recToken 非 hex64/24 詞不符、wrappedRec 前綴不符)或解包失敗 |
|
|
323
|
+
| `ERR_VAULT_INTERNAL` | 未預期的低階錯誤(`cause` 帶原錯) |
|
|
324
|
+
|
|
239
325
|
**伺服端(server/)+閘**
|
|
240
326
|
|
|
241
327
|
| 錯誤碼 | 拋出面 |
|
|
@@ -271,6 +357,17 @@ MIT。安全問題聯絡 security@tacet.ink(安全回報流程見 [SECURITY.md
|
|
|
271
357
|
[默·Tacet (tacet.ink)](https://tacet.ink), a zero-knowledge journal: the server stores only ciphertext
|
|
272
358
|
and salted hashes, and never learns your passphrase or the content of any note.
|
|
273
359
|
|
|
360
|
+
**High-level Vault API.** `@tacet-ink/journal-core/client/vault` (0.3.4+) wraps the primitives in a
|
|
361
|
+
single flow. `createVault(passphrase, { app: 'myapp', recovery: true })` derives every prefix from
|
|
362
|
+
`app` (zero config), wraps a random note key with Argon2id, and returns the `serverRecord` to upload
|
|
363
|
+
plus an opt-in 24-word recovery kit. `vault.encrypt(recordId, data)` / `vault.decrypt(recordId, blob)`
|
|
364
|
+
bind each record to `app:recordId` as AAD, so a ciphertext moved to another record fails to decrypt.
|
|
365
|
+
`unlockVault(passphrase, serverRecord, cfg)` reopens the vault on another device, and
|
|
366
|
+
`recoverVault(words, wrappedRec, cfg)` reopens it from the recovery kit. `deriveVaultPh1(passphrase, salt)`
|
|
367
|
+
produces the hex64 login credential your server passes to `loginRouteCore(env, store, ip, ph1)`.
|
|
368
|
+
Failures throw a `VaultError` with a stable `code`; a wrong passphrase and a tampered wrap
|
|
369
|
+
deliberately share one code, since telling them apart would create an oracle.
|
|
370
|
+
|
|
274
371
|
**Security model.**
|
|
275
372
|
|
|
276
373
|
- **Two key eras.** Before binding an account, notes are encrypted with a client-derived key
|
|
@@ -292,7 +389,7 @@ and salted hashes, and never learns your passphrase or the content of any note.
|
|
|
292
389
|
- **Key hygiene.** Any key that must be exported/wrapped is imported with `extractable=true`;
|
|
293
390
|
KEK and guest keys are permanently non-extractable.
|
|
294
391
|
|
|
295
|
-
**Server login wiring.** `loginRouteCore(store, ip, ph1)` ships with the ladder guard built in
|
|
392
|
+
**Server login wiring.** `loginRouteCore(env, store, ip, ph1)` ships with the ladder guard built in
|
|
296
393
|
(v0.2.4, external-review #8 ghost-account fix): on a current-value (PH2) miss it consults the
|
|
297
394
|
`ph2_ladder` table via the optional `AuthStore.ladderLookup` — a ladder hit returns the
|
|
298
395
|
existing account (no ghost creation); a ladder miss, an unconfigured lookup, or a
|
|
@@ -318,8 +415,10 @@ carrier: `node:crypto` ≥ Node 24.7 / hash-wasm in browsers, RFC 9106 test vect
|
|
|
318
415
|
PH1→PH2 login flow, inbound cipher/package validation and paired key-package selection
|
|
319
416
|
(`auth.ts`; PH2-UNIQUE conflict and session revocation are caller-owned wiring), per-IP
|
|
320
417
|
(IPv6 keyed per /64 prefix; IPv4-mapped addresses un-map to the native dotted IPv4 key) fixed-window rate limiting on D1 (`ratelimit.ts`), shared CORS/hash utilities. Since 0.3.0 the
|
|
321
|
-
package ships `dist/` (tsc-emitted ESM `.js` + `.d.ts`); every `exports` subpath resolves to dist
|
|
322
|
-
|
|
418
|
+
package ships `dist/` (tsc-emitted ESM `.js` + `.d.ts`); every `exports` subpath resolves to dist.
|
|
419
|
+
Since 0.3.4 the package is dist-only: `src/` and `scripts/` no longer ship (the tarball carries `dist/`,
|
|
420
|
+
`docs/`, `migrations/`, README and LICENSE). To read or audit the source, browse `src/` on GitHub
|
|
421
|
+
alongside the format spec and test vectors in `docs/`.
|
|
323
422
|
|
|
324
423
|
**Format spec & test vectors.** The byte-level wire format of every prefix family — payload layout
|
|
325
424
|
and field lengths, KDF parameters, AAD strings, and the in-band versioning contract — is specified
|
|
@@ -331,7 +430,7 @@ prefix family (`families.json`), known-answer vectors (RFC 9106 Argon2id, the ga
|
|
|
331
430
|
`scripts/generate-vectors.ts` against the real primitives and committed as frozen evidence; the
|
|
332
431
|
verify gate checks their presence and shape (section [26]) and does not regenerate them.
|
|
333
432
|
|
|
334
|
-
**Verification.** `npm run verify` runs
|
|
433
|
+
**Verification.** `npm run verify` runs 531 assertions against the real modules (no mocks; run it from a repo checkout — since 0.3.4 the published package is dist-only and carries no gate):
|
|
335
434
|
roundtrips, AAD tamper-evidence, extractability rules, era isolation, cross-prefix family
|
|
336
435
|
isolation, payload tampering, RFC 9106 KAT, a 200-vector BIP39 cross-check, and the
|
|
337
436
|
login ladder-guard behavior suite (external audit #8), and presence/shape checks for the format
|
|
@@ -341,7 +440,8 @@ spec and test vectors.
|
|
|
341
440
|
and Workers can import it directly — no bundler and no strip-types needed (the 0.2.x "must bundle"
|
|
342
441
|
requirement is retired). A source checkout can still run `src/` with `node --experimental-strip-types`.
|
|
343
442
|
In browsers, inject the hash-wasm Argon2id carrier via `setArgonLoader()`; Node ≥ 24.7 uses the
|
|
344
|
-
built-in `node:crypto`.
|
|
443
|
+
built-in `node:crypto`. The Quickstart opens with a runnable Vault walkthrough (create → encrypt →
|
|
444
|
+
unlock → recover → PH1); the low-level primitive samples are in the 使用 section.
|
|
345
445
|
|
|
346
446
|
**Honest limits.** The guest era is obfuscation-grade, not E2E. PH1 derivation is defined by each
|
|
347
447
|
product (fast hash or Argon2id); this layer only validates its hex64 form. Product concerns
|
package/dist/client/bip39.d.ts
CHANGED
|
@@ -9,3 +9,36 @@ export declare function wordsToRecToken(input: string | string[]): Promise<strin
|
|
|
9
9
|
export declare function recTokenToWords(recToken: string): Promise<string[] | null>;
|
|
10
10
|
/** 抄寫抽驗位置:24 詞中抽 4 個不重複位置(0-23),排序回傳(顯示順序穩定)。 */
|
|
11
11
|
export declare function spotCheckIndexes(): number[];
|
|
12
|
+
/** 24 詞固定長 tuple(BIP39 英文——復原套件詞形契約面;wordsToRecToken/recTokenToWords 的載體形)。 */
|
|
13
|
+
export type Bip39Words = [
|
|
14
|
+
string,
|
|
15
|
+
string,
|
|
16
|
+
string,
|
|
17
|
+
string,
|
|
18
|
+
string,
|
|
19
|
+
string,
|
|
20
|
+
string,
|
|
21
|
+
string,
|
|
22
|
+
string,
|
|
23
|
+
string,
|
|
24
|
+
string,
|
|
25
|
+
string,
|
|
26
|
+
string,
|
|
27
|
+
string,
|
|
28
|
+
string,
|
|
29
|
+
string,
|
|
30
|
+
string,
|
|
31
|
+
string,
|
|
32
|
+
string,
|
|
33
|
+
string,
|
|
34
|
+
string,
|
|
35
|
+
string,
|
|
36
|
+
string,
|
|
37
|
+
string
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* 詞形輸入正規化(normalizeWords 的公開收口):字串形=既有路(NFKC→trim→lower→split(/\s+/)→filter);
|
|
41
|
+
* 陣列形=每詞 NFKC→trim→lower 同構(wordsToRecToken 陣列腿同契約)、非字串元素入空串後濾除。
|
|
42
|
+
* 呼叫端:recoverVault 詞形入參先經此再走 wordsToRecToken——任何不符(錯數/詞表外/checksum)照舊 null。
|
|
43
|
+
*/
|
|
44
|
+
export declare function normalizeWordInput(input: string | string[]): string[];
|
package/dist/client/bip39.js
CHANGED
|
@@ -137,3 +137,15 @@ export function spotCheckIndexes() {
|
|
|
137
137
|
}
|
|
138
138
|
return pool.slice(0, 4).sort((a, b) => a - b);
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* 詞形輸入正規化(normalizeWords 的公開收口):字串形=既有路(NFKC→trim→lower→split(/\s+/)→filter);
|
|
142
|
+
* 陣列形=每詞 NFKC→trim→lower 同構(wordsToRecToken 陣列腿同契約)、非字串元素入空串後濾除。
|
|
143
|
+
* 呼叫端:recoverVault 詞形入參先經此再走 wordsToRecToken——任何不符(錯數/詞表外/checksum)照舊 null。
|
|
144
|
+
*/
|
|
145
|
+
export function normalizeWordInput(input) {
|
|
146
|
+
if (typeof input === 'string')
|
|
147
|
+
return normalizeWords(input);
|
|
148
|
+
return (Array.isArray(input) ? input : [])
|
|
149
|
+
.map((w) => (typeof w === 'string' ? w.normalize('NFKC').trim().toLowerCase() : ''))
|
|
150
|
+
.filter((w) => w.length > 0);
|
|
151
|
+
}
|
|
@@ -14,6 +14,10 @@ export type VaultErrorCode =
|
|
|
14
14
|
| 'ERR_VAULT_WRAP_UNKNOWN'
|
|
15
15
|
/** 家族辨識成功但解包失敗:密語/PIN 錯或包裹竄改(刻意不分)。 */
|
|
16
16
|
| 'ERR_VAULT_WRAP_MISMATCH'
|
|
17
|
+
/** 查詢別名字面:`isVaultError(e, 'ERR_VAULT_WRONG_PASSPHRASE')` 對 code === 'ERR_VAULT_WRAP_MISMATCH'
|
|
18
|
+
* 回 true(反之亦然)。拋出面零動——仍拋 WRAP_MISMATCH;失敗面刻意不細分密語錯/竄改
|
|
19
|
+
* (同 GCM 失敗——細分=oracle)。別名只在查詢面:本 union 判準仍以拋出碼為單一真相。 */
|
|
20
|
+
| 'ERR_VAULT_WRONG_PASSPHRASE'
|
|
17
21
|
/** 雙因子家族(jr2w./jr3d./jr4d.)缺 PIN。 */
|
|
18
22
|
| 'ERR_VAULT_PIN_REQUIRED'
|
|
19
23
|
/** 密語空值/非字串。 */
|
|
@@ -40,5 +44,4 @@ export declare class VaultError extends Error {
|
|
|
40
44
|
cause?: unknown;
|
|
41
45
|
});
|
|
42
46
|
}
|
|
43
|
-
/** 型別守衛:`isVaultError(e)` 或指定碼 `isVaultError(e, 'ERR_VAULT_DECRYPT')`。 */
|
|
44
47
|
export declare function isVaultError(e: unknown, code?: VaultErrorCode): e is VaultError;
|
|
@@ -13,7 +13,14 @@ export class VaultError extends Error {
|
|
|
13
13
|
this.code = code;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
/** 型別守衛:`isVaultError(e)` 或指定碼 `isVaultError(e, 'ERR_VAULT_DECRYPT')`。
|
|
16
|
+
/** 型別守衛:`isVaultError(e)` 或指定碼 `isVaultError(e, 'ERR_VAULT_DECRYPT')`。
|
|
17
|
+
* 查詢別名(雙向):`'ERR_VAULT_WRONG_PASSPHRASE'` 是 `'ERR_VAULT_WRAP_MISMATCH'` 的查詢面別名——
|
|
18
|
+
* 查詢帶別名、或錯誤實例帶別名(union 收了別名字面,手建實例可能),判定時兩側都投影到
|
|
19
|
+
* 拋出碼 WRAP_MISMATCH 再比;對其餘錯誤碼恆 false。拋出面恆 WRAP_MISMATCH(別名不產生第四碼形)。 */
|
|
20
|
+
const VAULT_ERROR_CODE_ALIAS = 'ERR_VAULT_WRONG_PASSPHRASE';
|
|
21
|
+
const canonVaultErrorCode = (c) => c === VAULT_ERROR_CODE_ALIAS ? 'ERR_VAULT_WRAP_MISMATCH' : c;
|
|
17
22
|
export function isVaultError(e, code) {
|
|
18
|
-
|
|
23
|
+
const q = canonVaultErrorCode(code);
|
|
24
|
+
const ec = e instanceof VaultError ? canonVaultErrorCode(e.code) : undefined;
|
|
25
|
+
return e instanceof VaultError && (q === undefined || ec === q);
|
|
19
26
|
}
|
package/dist/client/vault.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vault.ts — 高階 Vault API:createVault/unlockVault/recoverVault → VaultHandle。
|
|
2
|
+
* vault.ts — 高階 Vault API:createVault/unlockVault/recoverVault → VaultHandle+零設定前綴推導 resolver。
|
|
3
3
|
*
|
|
4
4
|
* 定位:低階原語(note-crypto.ts/argon2.ts)的**呼叫端**——additive-only,零改動原語語意。
|
|
5
5
|
* 一個 vault=一把隨機 256-bit noteKey(generateNoteKey)+伺服器存的包裹欄(wrapped+salt)
|
|
@@ -20,11 +20,48 @@
|
|
|
20
20
|
* tag:0x73 's'=字串(body=UTF-8)/0x6a 'j'=JSON(body=UTF-8(JSON.stringify))/0x62 'b'=位元組(body=原樣)。
|
|
21
21
|
*/
|
|
22
22
|
import { type Argon3Config } from './argon2.ts';
|
|
23
|
+
import { type Bip39Words } from './bip39.ts';
|
|
23
24
|
import { VaultError, isVaultError, type VaultErrorCode } from './vault-error.ts';
|
|
24
25
|
import { type ArgonCarrierKind, type ArgonCarrierOptions } from './argon-auto.ts';
|
|
25
26
|
export { VaultError, isVaultError, type VaultErrorCode };
|
|
26
27
|
export { ensureArgonCarrier, currentArgonCarrier, resetArgonCarrier, DEFAULT_ARGON_CHAIN, type ArgonCarrierKind, type ArgonCarrierChoice, type ArgonCarrierOptions, type ArgonFallbackEvent, } from './argon-auto.ts';
|
|
27
28
|
export { argonWorkerSource, createBlobArgonWorker, type ArgonWorkerLike } from './argon-worker.ts';
|
|
29
|
+
/** 復原套件規格形(= 既顯式物件形;resolver 對 app 推導同形輸出——名稱是接線層措語,型別零新增)。 */
|
|
30
|
+
export type VaultRecoverySpec = {
|
|
31
|
+
wrapRec: string;
|
|
32
|
+
recKekHkdf: string;
|
|
33
|
+
recKekSalt?: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* resolveVaultConfig(input):零設定前綴推導(resolver)。
|
|
37
|
+
* 輸入 `{ app, ...任意顯式欄位 }`,輸出完整 VaultConfig+defaulted 旗標:
|
|
38
|
+
* `defaulted`=真時 cipher/argon/legacy/recovery 四組**整組**由 app 決定性推導(10 欄,recovery opt-in 時含)——
|
|
39
|
+
* 命名自定(語意=resolveVaultConfig:`{app}`+零設定前綴推導):前綴零手填、家族尾碼恆固定(契約三要素 app+尾碼+佈局;
|
|
40
|
+
* 帶內版本化:尾碼代即版本代,升級=新尾碼;家族佈局不隨字面——README 前綴契約節同源)。
|
|
41
|
+
* `defaulted`=假時輸入即顯式全配置(回歸 diff 恆 0——validateConfig 逐對檢查照守)。
|
|
42
|
+
*
|
|
43
|
+
* 推導表(app 契約 `^[A-Za-z0-9_-]+$`——前綴字面=線上 ASCII 契約;**契約閘=零設定(推導)路徑專載**:
|
|
44
|
+
* 零設定路徑上 app 在場但含契約外字元=ERR_VAULT_CONFIG fail-closed 不猜;顯式全配置路徑
|
|
45
|
+
* app 字面照 0.3.3 既有契約零驗形(非 ASCII/帶點 app 走顯式全配置照舊)——契約閘不跨路徑加寬):
|
|
46
|
+
* cipher='foo1v.'/wrap4='foo4w.'/wrapDual4='foo4d.'/pinSalt3Prefix='foo3s:'(鹽域 ':' 同 tacet 形)
|
|
47
|
+
* wrap3='foo3w.'/wrapDual3='foo3d.'/legacy wrap='foo1w.'/wrapDual='foo2w.'/pinSaltPrefix='foo1s.'
|
|
48
|
+
* recovery(`recovery: true` 時)wrapRec='foo1r.'/recKekHkdf='fookek1'(KEK info 域帶 k 代——刻意
|
|
49
|
+
* 異於 wrapRec 字面:HKDF info 域與前綴域恆非前綴關係,鹽外置 payload 邊界不重疊);
|
|
50
|
+
* wrap3/wrapDual3 舊世代讀面同由 app 推導(`app + '3w.'`/`app + '3d.'`——app 族半顯式 legacy 槽的推導殘部)。
|
|
51
|
+
* `foo`=示例形;任兩推導值互不為前綴(startsWith 唯一性前提);尾碼代=家族表 README 行。
|
|
52
|
+
*
|
|
53
|
+
* 顯式路徑判別:`argon` 或 `cipher` 任一在場=顯式全配置路徑(resolver 不再推導任何欄位——
|
|
54
|
+
* 顯式欄位優先、缺欄照 validateConfig 拒=opt-in 母型;app 契約閘不跨路徑加寬——**契約閘=
|
|
55
|
+
* 零設定路徑專載**(顯式全配置路徑 app 字面照 0.3.3 契約零驗;`resolveVaultConfig(opts)`
|
|
56
|
+
* 三入口單點接線共用)。
|
|
57
|
+
* recovery 物件形同判(呼叫端自管面)。半顯式態(argon 組推導+legacy 物件槽顯式欄位共存):
|
|
58
|
+
* 推導值與顯式值同進前綴互斥檢查(validateConfig——例 `legacy: { wrap: 'foo4w.bait' }` 與推導
|
|
59
|
+
* wrap4 'foo4w.' 互為前綴=CONFIG 拒;歧義配置不猜。
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveVaultConfig(input: VaultOptions | VaultConfigInput): {
|
|
62
|
+
config: VaultConfig;
|
|
63
|
+
defaulted: boolean;
|
|
64
|
+
};
|
|
28
65
|
export interface VaultConfig {
|
|
29
66
|
/** 應用域(AAD 前段):記錄密文 AAD=`<app>:<recordId>`。必填非空。 */
|
|
30
67
|
app: string;
|
|
@@ -33,18 +70,37 @@ export interface VaultConfig {
|
|
|
33
70
|
/** Argon2id 家族前綴:`wrap4` 必填(寫面);`wrapDual4`+`pinSalt3Prefix`=PIN 寫面;
|
|
34
71
|
* `wrap3`/`wrapDual3`=舊世代讀面(unlock+upgrade 來源)。`wrapShare3` 本 API 不用。 */
|
|
35
72
|
argon: Argon3Config;
|
|
36
|
-
/** PBKDF2 舊世代讀面(jr1w.=`wrap`;jr2w.=`wrapDual`+`pinSaltPrefix`)。只讀不寫。
|
|
73
|
+
/** PBKDF2 舊世代讀面(jr1w.=`wrap`;jr2w.=`wrapDual`+`pinSaltPrefix`)。只讀不寫。
|
|
74
|
+
* `wrap: false`=舊契約回落腿關閉(零設定 vault 預設語意——identity 綁域的 jr1w. 復原套件不屬本 API)。 */
|
|
37
75
|
legacy?: {
|
|
38
|
-
wrap?: string;
|
|
76
|
+
wrap?: string | false;
|
|
39
77
|
wrapDual?: string;
|
|
40
78
|
pinSaltPrefix?: string;
|
|
41
79
|
};
|
|
42
|
-
/** 復原套件(jr1r. HKDF
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
80
|
+
/** 復原套件(jr1r. HKDF 世代;兩欄一體)。物件形=顯式(呼叫端自管前綴與鹽域);
|
|
81
|
+
* true=opt-in 零設定(wrapRec/recKekHkdf 由 resolver 以 app 推導;recKekSalt 未指定=零鹽契約);
|
|
82
|
+
* false/undefined=不產生復原套件(recoverVault 拒)。 */
|
|
83
|
+
recovery?: VaultRecoverySpec | boolean;
|
|
84
|
+
}
|
|
85
|
+
/** VaultConfigInput:resolveVaultConfig 輸入形(三入口共用——零設定 `{app}` 與顯式全配置同形進位;
|
|
86
|
+
* pin/carrier=入口進位欄,resolver 不消費)。 */
|
|
87
|
+
export interface VaultConfigInput {
|
|
88
|
+
/** app 契約 `^[A-Za-z0-9_-]+$`:零設定路徑 fail-closed(缺 app 亦拒——推導無從);顯式路徑放行顯式全配置。 */
|
|
89
|
+
app?: string;
|
|
90
|
+
/** 顯式記錄密文前綴(在場=顯式全配置路徑——resolver 不推導)。 */
|
|
91
|
+
cipher?: string;
|
|
92
|
+
/** 顯式 Argon3 家族前綴組(在場=顯式全配置路徑)。 */
|
|
93
|
+
argon?: Argon3Config;
|
|
94
|
+
/** 舊世代讀面 opt-in:true=全推導;false/undefined=falsable 預設 `{ wrap: false }`(wrap 舊契約回落腿關閉);
|
|
95
|
+
* 物件形=顯式欄位優先、缺欄由 app 推導(`wrap: false` 原樣保留)。 */
|
|
96
|
+
legacy?: VaultConfig['legacy'] | boolean;
|
|
97
|
+
/** 復原套件 opt-in:true=resolver 推導(wrapRec=`<app>1r.`、recKekHkdf=`<app>kek1`、零鹽契約);
|
|
98
|
+
* 物件形=顯式(呼叫端自管前綴與鹽域——=顯式路徑判別);false/undefined=無(recoverVault 拒)。 */
|
|
99
|
+
recovery?: VaultRecoverySpec | boolean;
|
|
100
|
+
/** PIN 第二因子(入口進位欄——resolver 不消費;VaultOptions 同欄)。 */
|
|
101
|
+
pin?: string;
|
|
102
|
+
/** Argon2id 載體選項(入口進位欄——resolver 不消費;VaultOptions 同欄)。 */
|
|
103
|
+
carrier?: ArgonCarrierOptions;
|
|
48
104
|
}
|
|
49
105
|
export interface VaultOptions extends VaultConfig {
|
|
50
106
|
/** PIN 第二因子:create=寫 jr4d.;unlock=雙因子家族必帶。 */
|
|
@@ -60,6 +116,9 @@ export interface VaultServerRecord {
|
|
|
60
116
|
/** 復原套件:recToken 只顯示一次(可經 bip39.ts 轉 24 詞);伺服器存 wrappedRec+recTokenHash。 */
|
|
61
117
|
export interface VaultRecoveryKit {
|
|
62
118
|
recToken: string;
|
|
119
|
+
/** 24 詞形(=recTokenToWords(recToken) 的 BIP39 轉寫;伺服器零存——bip39 轉寫層可逆再導出;
|
|
120
|
+
* recToken hex64 欄保留=雙形相容既有 kit)。 */
|
|
121
|
+
words: Bip39Words;
|
|
63
122
|
wrappedRec: string;
|
|
64
123
|
recTokenHash: string;
|
|
65
124
|
}
|
|
@@ -94,14 +153,34 @@ export interface VaultHandle {
|
|
|
94
153
|
pin?: string;
|
|
95
154
|
}): Promise<VaultServerRecord>;
|
|
96
155
|
}
|
|
97
|
-
/**
|
|
98
|
-
|
|
156
|
+
/**
|
|
157
|
+
* deriveVaultPh1(passphrase, ph1Salt):高階 Vault API 的 PH1 登入憑證派生(hex64)。
|
|
158
|
+
* 本體委派 `derivePh1ArgonV3(pass, ph1Salt)`(PH1 v3:Argon2id(normalizePassphrase(pass), 鹽, m=64MiB, t=3, p=1)→hex64)
|
|
159
|
+
* ——語意單一真相同在 argon2.ts,本函式只是 vault 子路徑的入口收口位(呼叫端免跨 import argon2)。
|
|
160
|
+
*
|
|
161
|
+
* ⚠️ 契約三行(README PH1 節同款):
|
|
162
|
+
* 1. 鹽一經選定恆固定(per-product)——PH2 UNIQUE 約束:同一密語必須恆生同一 PH2;
|
|
163
|
+
* per-user 鹽會摧毀它。改鹽=帶內版本化換鹽尾碼重遷移,禁原地改語意。
|
|
164
|
+
* 2. `PH1_V3_SALT`('tacet-ph1-v2')禁當 saltArg 餵 v2 派生面 `derivePh1Argon`——
|
|
165
|
+
* 反之亦然:同鹽域值跨面餵入=NFKC-effective 密語與 v2 raw 密語同值撞 PH2(鹽撞位警告)。
|
|
166
|
+
* 3. 其他產品用自有鹽域——禁硬編 tacet 鹽當預設;必須顯式帶入 `ph1Salt`。
|
|
167
|
+
*/
|
|
168
|
+
export declare function deriveVaultPh1(passphrase: string, ph1Salt: Uint8Array): Promise<string>;
|
|
169
|
+
/** 新 vault:隨機 noteKey+最新家族包裹(pin=<app>4d.,否則 <app>4w.)+選配復原套件(<app>1r.)。
|
|
170
|
+
* 前綴零手填態(resolver):`{ app }` 最小設定——全前綴由 app 決定性推導(resolveVaultConfig;
|
|
171
|
+
* `recovery: true` opt-in、PIN 帶值=同族尾碼推導 <app>4d. 不拒);顯式 `argon`/`cipher` 在場
|
|
172
|
+
* =顯式全配置路徑(零設定語意退出——缺欄照 validateConfig 拒)。輸入形 VaultOptions | VaultConfigInput。 */
|
|
173
|
+
export declare function createVault(passphrase: string, opts: VaultOptions | VaultConfigInput): Promise<{
|
|
99
174
|
vault: VaultHandle;
|
|
100
175
|
serverRecord: VaultServerRecord;
|
|
101
176
|
recovery: VaultRecoveryKit | null;
|
|
102
177
|
}>;
|
|
103
178
|
/** 解鎖:serverRecord(wrapped+salt 成對)按前綴辨識家族(新→舊次序),舊世代照解;
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
export declare function
|
|
179
|
+
* 前綴零手填態(resolver):整組前綴缺省=全前綴由 app 推導(resolveVaultConfig);顯式欄位
|
|
180
|
+
* 在場=顯式全配置路徑(零推導)。needsUpgrade() 為真時呼叫端可於同一動線
|
|
181
|
+
* upgrade(passphrase) 靜默重包(Argon 遷移契約同構)。輸入形 VaultOptions | VaultConfigInput。 */
|
|
182
|
+
export declare function unlockVault(passphrase: string, serverRecord: VaultServerRecord, opts: VaultOptions | VaultConfigInput): Promise<VaultHandle>;
|
|
183
|
+
/** 復原:recToken(hex64)+wrappedRec(<app>1r.)→ handle(無包裹欄——呼叫端隨即 changePassphrase 設新密語)。
|
|
184
|
+
* 前綴零手填態同上:recovery 未配置=ERR_VAULT_CONFIG(零鹽選擇不可靜默——opt-in 鐵律;resolver 對
|
|
185
|
+
* `recovery: true` 推導零鹽契約、物件形=顯式零動)。輸入形 VaultOptions | VaultConfigInput。 */
|
|
186
|
+
export declare function recoverVault(recToken: string | string[], wrappedRec: string, opts: VaultOptions | VaultConfigInput): Promise<VaultHandle>;
|