@tacet-ink/journal-core 0.2.5 → 0.3.1
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 +38 -15
- package/dist/client/argon-auto.d.ts +47 -0
- package/dist/client/argon-auto.js +226 -0
- package/dist/client/argon-worker.d.ts +31 -0
- package/dist/client/argon-worker.js +147 -0
- package/dist/client/argon2.d.ts +167 -0
- package/dist/client/argon2.js +410 -0
- package/dist/client/bip39.d.ts +11 -0
- package/dist/client/bip39.js +139 -0
- package/dist/client/keys.d.ts +15 -0
- package/dist/client/keys.js +30 -0
- package/dist/client/note-crypto.d.ts +203 -0
- package/dist/client/note-crypto.js +600 -0
- package/dist/client/pinlock.d.ts +38 -0
- package/dist/client/pinlock.js +84 -0
- package/dist/client/vault-error.d.ts +44 -0
- package/dist/client/vault-error.js +19 -0
- package/dist/client/vault.d.ts +107 -0
- package/dist/client/vault.js +391 -0
- package/dist/client/vendor/hash-wasm-argon2.d.ts +14 -0
- package/dist/client/vendor/hash-wasm-argon2.js +160 -0
- package/dist/client/wordlist.d.ts +7 -0
- package/dist/client/wordlist.js +264 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +17 -0
- package/dist/server/auth.d.ts +99 -0
- package/dist/server/auth.js +158 -0
- package/dist/server/cors.d.ts +10 -0
- package/dist/server/cors.js +21 -0
- package/dist/server/env.d.ts +6 -0
- package/dist/server/env.js +1 -0
- package/dist/server/hash.d.ts +11 -0
- package/dist/server/hash.js +26 -0
- package/dist/server/ladder.d.ts +49 -0
- package/dist/server/ladder.js +50 -0
- package/dist/server/ratelimit.d.ts +32 -0
- package/dist/server/ratelimit.js +63 -0
- package/docs/format-spec.md +369 -0
- package/docs/vectors/bip39.json +1063 -0
- package/docs/vectors/families.json +594 -0
- package/docs/vectors/kat.json +145 -0
- package/package.json +68 -17
- package/scripts/verify-core-crypto.ts +406 -11
- package/src/client/argon-auto.ts +224 -0
- package/src/client/argon-worker.ts +147 -0
- package/src/client/argon2.ts +7 -2
- package/src/client/vault-error.ts +51 -0
- package/src/client/vault.ts +486 -0
- package/src/client/vendor/hash-wasm-argon2.ts +27 -0
|
@@ -69,6 +69,8 @@ import {
|
|
|
69
69
|
derivePh1ArgonV3,
|
|
70
70
|
PH1_V2_SALT,
|
|
71
71
|
PH1_V3_SALT,
|
|
72
|
+
ARGON_RFC9106_EXPECTED,
|
|
73
|
+
setArgonLoader,
|
|
72
74
|
type Argon3Config,
|
|
73
75
|
} from '../src/client/argon2.ts';
|
|
74
76
|
import { makeKeyStore } from '../src/client/keys.ts';
|
|
@@ -97,6 +99,19 @@ import {
|
|
|
97
99
|
PH2_LADDER_KIND_V2,
|
|
98
100
|
makePh2LadderStore,
|
|
99
101
|
} from '../src/server/ladder.ts';
|
|
102
|
+
import {
|
|
103
|
+
createVault,
|
|
104
|
+
unlockVault,
|
|
105
|
+
recoverVault,
|
|
106
|
+
VaultError,
|
|
107
|
+
isVaultError,
|
|
108
|
+
currentArgonCarrier,
|
|
109
|
+
resetArgonCarrier,
|
|
110
|
+
argonWorkerSource,
|
|
111
|
+
type VaultOptions,
|
|
112
|
+
type ArgonWorkerLike,
|
|
113
|
+
} from '../src/client/vault.ts';
|
|
114
|
+
import { hashWasmArgon2Factory, HASH_WASM_ARGON2_SHA256, HASH_WASM_VERSION } from '../src/client/vendor/hash-wasm-argon2.ts';
|
|
100
115
|
|
|
101
116
|
type Row19 = { account_id: string; ph2_kind: string };
|
|
102
117
|
type Env19 = { DB: { prepare(sql: string): { bind(...params: unknown[]): { first(): Promise<Row19 | null>; run(): Promise<void> } } } };
|
|
@@ -142,15 +157,16 @@ async function A(name: string, cond: boolean | Promise<boolean>, detail = ''): P
|
|
|
142
157
|
// 帳面契約:--only 態總帳顯形「本輪 X/選段帳 N(N 案非本輪載)」+OK 標記帶 (--ONLY …) 尾碼;skip 段帳面恆列「未跑非通過」;[24]/[25] runner 自證段隨每輪在場。
|
|
143
158
|
// 段標記 helper(secOpen):全跑態輸出位元組恆等原 console.log('\n[N] …');--only 態僅選中段輸出段頭。
|
|
144
159
|
// 毒化矩陣內層 rerun(execFileSync+POISON_GATE_INNER=1)不傳遞 --only=內層恆全帳(POISON_GATE_INNER 語意不變)。
|
|
145
|
-
// 段帳=執行帳(healthy-repo 全跑 runtime 真值,2026-10-10 實測
|
|
160
|
+
// 段帳=執行帳(healthy-repo 全跑 runtime 真值,2026-10-10 實測 506——A3 [27] +14);帳面語意契約(逐段「案 X/帳」
|
|
146
161
|
// 對 secRan 執行值顯形):執行 < 帳 的段=本環境條件態未行使(非 skip 態),--only 期望算術對齊本帳。
|
|
147
162
|
// [23] 執行 18 帳 21=[23]④ lookup 未配置(0.2.3 單查恆等)/[23]⑤ fail-open(表缺席)兩情境
|
|
148
|
-
// 在本機樹由 [8]
|
|
163
|
+
// 在本機樹由 [8](登入憑證 Argon2id 派生)內登入 lane 的訊號面真行使(try/catch 異常面)——車道分流非帳面虛報。
|
|
149
164
|
// secEnter 跑行為脫鉤 secCases;secCases 鍵只承載段存在/帳面/--only 判準的登記義務
|
|
150
165
|
//(secOpen(22,…) 無段標記行=慣例 skip 段)。缺帳防線(teeth):secRan 實跑段必已登記——
|
|
151
|
-
// for (const [k] of secRan) if (!secCases[k] && k !== 22) FAIL
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
// for (const [k] of secRan) if (!secCases[k] && k !== 22) FAIL(負向斷言本體在 [25] 段
|
|
167
|
+
//——secCases 全摘即 RED);反向缺席段=帳面誠實「案 0/N · skip」。
|
|
168
|
+
// secCases 載體三群:[24]/[25] self-account+--only 段存在面+teeth(段帳由 secRan 執行自記)。
|
|
169
|
+
const secCases: Record<number, number> = { 1: 3, 2: 7, 3: 9, 4: 4, 5: 3, 6: 20, 7: 27, 8: 8, 9: 22, 10: 5, 11: 41, 12: 50, 13: 39, 14: 18, 15: 59, 16: 15, 17: 14, 18: 39, 19: 17, 20: 47, 21: 10, 23: 18, 24: 5, 25: 4, 26: 8, 27: 14 };
|
|
154
170
|
function parseOnly(spec: string): { err: string | null; set: number[] } {
|
|
155
171
|
const set = new Set<number>();
|
|
156
172
|
const specStr = spec.trim();
|
|
@@ -2524,7 +2540,7 @@ secOpen(21, '[21] CI 供應鏈面(npm ci --ignore-scripts+dependabot runner
|
|
|
2524
2540
|
await A('[21] publish.yml:npm ci --ignore-scripts(tag 驅動無 actor 面;typecheck+verify 先行=腳本面零在場)',
|
|
2525
2541
|
srcPy21.includes('npm ci --ignore-scripts'));
|
|
2526
2542
|
} else {
|
|
2527
|
-
await A('[21] CI 源碼面:.github 缺席環境(tarball 發行樹常態)=顯性 SKIP(workflow 斷言 4 收斂為 1 SKIP 行——消費端計數帳
|
|
2543
|
+
await A('[21] CI 源碼面:.github 缺席環境(tarball 發行樹常態)=顯性 SKIP(workflow 斷言 4 收斂為 1 SKIP 行——消費端計數帳 503)',
|
|
2528
2544
|
true, 'no .github dir — consumption tree face');
|
|
2529
2545
|
}
|
|
2530
2546
|
// 摘除自證(t_760f44e8 治理掃蕩補漏;fs 原語缺席環境=顯性 FAIL 形自守衛——非 silent true)
|
|
@@ -2789,6 +2805,385 @@ secOpen(23, '[23] loginRouteCore ladder 查表守衛(外審 #8 幽靈帳——
|
|
|
2789
2805
|
}
|
|
2790
2806
|
|
|
2791
2807
|
};
|
|
2808
|
+
// ── 26. 格式規格+測試向量(docs/format-spec.md+docs/vectors/*.json) ──
|
|
2809
|
+
//
|
|
2810
|
+
// 單向契約:源碼 → scripts/generate-vectors.ts → JSON(凍結證據,commit 入 repo)→ 本段只檢存在+shape。
|
|
2811
|
+
// 本段不依賴產生器、不重算 KDF(向量含隨機 iv/鹽=重算即漂移);BIP39 是確定性轉寫,逐組對照現行原語。
|
|
2812
|
+
// teeth:payload 位元組長度=b64 解碼真值、fields 拼接=payload 逐位元組、布局長度=規格表列、
|
|
2813
|
+
// 欄位名逐一在規格檔、凍結 blob 逐字=本閘 KAT 字面([14]/[17]/[20])、RFC 9106 期望值=argon2.ts 常數。
|
|
2814
|
+
secOpen(26, '[26] 格式規格+測試向量(docs/format-spec.md+docs/vectors 存在性+shape)'); if (secEnter(26)) {
|
|
2815
|
+
{
|
|
2816
|
+
const readJson26 = async (rel: string): Promise<Record<string, unknown> | null> => {
|
|
2817
|
+
try { return JSON.parse(await srcOf(rel)) as Record<string, unknown>; } catch { return null; }
|
|
2818
|
+
};
|
|
2819
|
+
const spec26 = await srcOf('../docs/format-spec.md').catch(() => '');
|
|
2820
|
+
const fam26 = await readJson26('../docs/vectors/families.json');
|
|
2821
|
+
const kat26 = await readJson26('../docs/vectors/kat.json');
|
|
2822
|
+
const bip26 = await readJson26('../docs/vectors/bip39.json');
|
|
2823
|
+
type Vec26 = { prefix: string; family: string; layout: string; kdf: string; aad: string; inputs: Record<string, unknown>; wire: string; payload_len: number; fields: { name: string; offset: number; len: number; hex: string }[] };
|
|
2824
|
+
const vecs26 = (Array.isArray(fam26?.vectors) ? fam26!.vectors : []) as Vec26[];
|
|
2825
|
+
const HEAD26 = ['format', 'package_version', 'spec', 'generator', 'note'];
|
|
2826
|
+
await A('[26] 規格檔+三向量檔在場且 JSON parse;頂層共通欄齊(format=journal-core-vectors/1、spec/generator 路徑字面——產生器不被閘依賴,發行包不帶產生器)',
|
|
2827
|
+
spec26.length > 0 && !!fam26 && !!kat26 && !!bip26
|
|
2828
|
+
&& [fam26, kat26, bip26].every((j) => HEAD26.every((k) => typeof j![k] === 'string') && j!.format === 'journal-core-vectors/1'
|
|
2829
|
+
&& j!.spec === 'docs/format-spec.md' && j!.generator === 'scripts/generate-vectors.ts'));
|
|
2830
|
+
// 規格表前綴集(§1 家族逐條表:| `prefix` | 家族 | 布局 | payload | KDF | AAD |)
|
|
2831
|
+
const specRows26 = spec26.split('\n').filter((l) => /^\| `jr[0-9a-z]{2}\.`(([^|]*))? \|/.test(l) && /\| (content|salt-external|salt-embedded) \|/.test(l));
|
|
2832
|
+
const specPrefixes26 = new Set(specRows26.map((l) => l.match(/^\| `(jr[0-9a-z]{2}\.)`/)![1]));
|
|
2833
|
+
const vecPrefixes26 = new Set(vecs26.map((v) => v.prefix));
|
|
2834
|
+
await A('[26] 家族覆蓋雙向:規格家族表 16 列前綴集=families.json 前綴集(15 前綴;jr1w. 兩世代各一組)',
|
|
2835
|
+
specRows26.length === 16 && vecs26.length === 16 && specPrefixes26.size === 15
|
|
2836
|
+
&& [...specPrefixes26].every((p) => vecPrefixes26.has(p)) && [...vecPrefixes26].every((p) => specPrefixes26.has(p)));
|
|
2837
|
+
const VKEYS26 = ['prefix', 'family', 'layout', 'kdf', 'aad', 'inputs', 'wire', 'payload_len', 'fields'];
|
|
2838
|
+
await A('[26] families 位元組 shape:payload_len=wire b64 解碼真長;fields offset 連續、len 總和=payload_len、hex 拼接=payload 逐位元組',
|
|
2839
|
+
vecs26.length > 0 && vecs26.every((v) => {
|
|
2840
|
+
if (!VKEYS26.every((k) => k in v) || !v.wire.startsWith(v.prefix)) return false;
|
|
2841
|
+
let payload: Uint8Array;
|
|
2842
|
+
try { payload = unb64Mod(v.wire.slice(v.prefix.length)); } catch { return false; }
|
|
2843
|
+
let off = 0;
|
|
2844
|
+
for (const f of v.fields) { if (f.offset !== off || f.hex.length !== f.len * 2) return false; off += f.len; }
|
|
2845
|
+
return payload.length === v.payload_len && off === v.payload_len && v.fields.map((f) => f.hex).join('') === hex(payload);
|
|
2846
|
+
}));
|
|
2847
|
+
await A('[26] 布局長度=規格:salt-external 92/salt-embedded 108(pinSalt 頭 16)/content 28+UTF8(plaintext);且規格家族表該前綴列含同布局+同長度',
|
|
2848
|
+
vecs26.length > 0 && vecs26.every((v) => {
|
|
2849
|
+
const want = v.layout === 'salt-external' ? 92 : v.layout === 'salt-embedded' ? 108 : v.layout === 'content' ? 28 + enc.encode(String(v.inputs.plaintext)).length : -1;
|
|
2850
|
+
const lenCol = v.layout === 'content' ? '28+n' : String(want);
|
|
2851
|
+
const head = v.layout === 'salt-embedded' ? v.fields[0]?.name === 'pinSalt' && v.fields[0]?.len === 16 : v.fields[0]?.name === 'iv';
|
|
2852
|
+
return want === v.payload_len && head && specRows26.some((l) => l.startsWith('| `' + v.prefix + '`') && l.includes('| ' + v.layout + ' | ' + lenCol + ' |'));
|
|
2853
|
+
}));
|
|
2854
|
+
const KKEYS26 = ['rfc9106', 'frozen_blobs', 'credentials', 'expected_tag_hex', 'expected_note_key_hex', 'expected_hex', 'samples', 'entropy_hex', 'checksum_hex', 'words', 'source'];
|
|
2855
|
+
await A('[26] 欄位名對規格:families 每筆欄名+fields 子欄(name/offset/len/hex)+kat/bip39 欄名,逐一以 `欄名` 形出現在 format-spec.md',
|
|
2856
|
+
spec26.length > 0 && [...HEAD26, ...VKEYS26, 'name', 'offset', 'len', 'hex', ...KKEYS26].every((k) => spec26.includes('`' + k + '`'))
|
|
2857
|
+
&& KKEYS26.slice(0, 3).every((k) => !!kat26 && k in kat26) && !!bip26 && 'samples' in bip26);
|
|
2858
|
+
const gate26 = await srcOf('./verify-core-crypto.ts');
|
|
2859
|
+
const frozen26 = (Array.isArray(kat26?.frozen_blobs) ? kat26!.frozen_blobs : []) as { prefix: string; wire: string; payload_len: number; expected_note_key_hex: string; source: string }[];
|
|
2860
|
+
const rfc26 = (kat26?.rfc9106 ?? {}) as Record<string, unknown>;
|
|
2861
|
+
const creds26 = (Array.isArray(kat26?.credentials) ? kat26!.credentials : []) as { kind: string; expected_hex: string }[];
|
|
2862
|
+
await A('[26] kat 對帳:RFC 9106 expected_tag_hex=ARGON_RFC9106_EXPECTED(argon2.ts 常數);凍結 blob 7 筆逐字=本閘 KAT 字面([14]/[17]/[20])且 payload_len=解碼真長;憑證樣本 5 筆 hex64',
|
|
2863
|
+
(await import('../src/client/argon2.ts')).ARGON_RFC9106_EXPECTED === rfc26.expected_tag_hex
|
|
2864
|
+
&& frozen26.length === 7 && frozen26.every((b) => gate26.includes("'" + b.wire + "'") && b.wire.startsWith(b.prefix)
|
|
2865
|
+
&& unb64Mod(b.wire.slice(b.prefix.length)).length === b.payload_len && /^[0-9a-f]{64}$/.test(b.expected_note_key_hex))
|
|
2866
|
+
&& creds26.length === 5 && creds26.every((c) => /^[0-9a-f]{64}$/.test(c.expected_hex)));
|
|
2867
|
+
const samples26 = (Array.isArray(bip26?.samples) ? bip26!.samples : []) as { entropy_hex: string; checksum_hex: string; words: string[]; source: string }[];
|
|
2868
|
+
await A('[26] bip39 抽樣 ≥32 組:entropy hex64+24 詞+checksum=SHA-256(entropy)[0];逐組對照現行轉寫原語(recTokenToWords 逐詞相等——確定性轉寫非 KDF 重算)',
|
|
2869
|
+
samples26.length >= 32 && (await (async () => {
|
|
2870
|
+
for (const s of samples26) {
|
|
2871
|
+
if (!/^[0-9a-f]{64}$/.test(s.entropy_hex) || s.words?.length !== 24) return false;
|
|
2872
|
+
const cs = new Uint8Array(await crypto.subtle.digest('SHA-256', hexToBytes(s.entropy_hex) as BufferSource))[0];
|
|
2873
|
+
if (cs.toString(16).padStart(2, '0') !== s.checksum_hex) return false;
|
|
2874
|
+
if ((await bip.recTokenToWords(s.entropy_hex))?.join(' ') !== s.words.join(' ')) return false;
|
|
2875
|
+
}
|
|
2876
|
+
return true;
|
|
2877
|
+
})()));
|
|
2878
|
+
const pkg26 = JSON.parse(await srcOf('../package.json')) as { files?: string[] };
|
|
2879
|
+
await A('[26] 發行面:package.json files 含 docs(tarball 帶規格+向量——消費端可對帳)+產生器排除(!scripts/generate-vectors.ts——[22] scripts 封閉集 2 檔不動)',
|
|
2880
|
+
Array.isArray(pkg26.files) && pkg26.files.includes('docs') && pkg26.files.includes('!scripts/generate-vectors.ts'));
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
// ── 27. 高階 Vault API(src/client/vault.ts+argon-auto.ts+argon-worker.ts+vendor hash-wasm) ──
|
|
2885
|
+
//
|
|
2886
|
+
// 行為面(對真模組):create→encrypt→decrypt 三資料形 roundtrip、AAD=`<app>:<recordId>` 以低階原語
|
|
2887
|
+
// 手解對帳、unlockVault 新前綴(jr4w.)/舊前綴(jr3w. Argon、jr1w. PBKDF2)、upgrade 同鑰驗證重包、
|
|
2888
|
+
// changePassphrase 週期(含 PIN jr4d. 進出)、復原套件(jr1r.)、VaultError 毒化(搬列 AAD/竄改/
|
|
2889
|
+
// 形不符/配置缺欄)、載體覆寫顯形(setArgonLoader 注入優先+錯值載體 KAT 拒用)、真 Worker 載體
|
|
2890
|
+
//(node worker_threads 轉接層跑 argonWorkerSource 原文——瀏覽器 blob 形同源碼)、回退鏈(Worker 失效→
|
|
2891
|
+
// inline+onFallback;無回退鏈=KDF_UNSUPPORTED;派生期載體失效歸因不誤報 WRAP_MISMATCH)、vendor 孿生
|
|
2892
|
+
//(去縮排原文 sha256=檔內常數=閘釘選值)、零 runtime deps+exports 子路徑+分層單向。
|
|
2893
|
+
// 收尾恆 resetArgonCarrier()+setArgonLoader(null)——後續段零載體殘留。
|
|
2894
|
+
secOpen(27, '[27] 高階 Vault API(roundtrip 三資料形/新舊前綴解鎖/upgrade/換密語/復原/VaultError 毒化/載體覆寫+Worker 回退鏈/vendor 孿生)'); if (secEnter(27)) {
|
|
2895
|
+
{
|
|
2896
|
+
const codeOf27 = async (p: Promise<unknown>): Promise<string> => {
|
|
2897
|
+
try { await p; return 'OK'; } catch (e) { return e instanceof VaultError ? e.code : 'NON-VAULT:' + String((e as Error)?.message ?? e); }
|
|
2898
|
+
};
|
|
2899
|
+
const fam27 = (h: { family: unknown }): string | null => h.family as string | null; // getter 讀值(禁 TS 跨 await 窄化)
|
|
2900
|
+
const bytesEq27 = (a: unknown, b: Uint8Array): boolean =>
|
|
2901
|
+
a instanceof Uint8Array && a.length === b.length && a.every((x, i) => x === b[i]);
|
|
2902
|
+
const CFG27: VaultOptions = {
|
|
2903
|
+
app: 'tacet-vault27',
|
|
2904
|
+
cipher: 'jr1v.',
|
|
2905
|
+
argon: { wrap4: 'jr4w.', wrap3: 'jr3w.', wrapDual4: 'jr4d.', wrapDual3: 'jr3d.', pinSalt3Prefix: 'tacet-note-pin3:' },
|
|
2906
|
+
legacy: { wrap: 'jr1w.', wrapDual: 'jr2w.', pinSaltPrefix: 'tacet-note-pin1:' },
|
|
2907
|
+
recovery: { wrapRec: 'jr1r.', recKekHkdf: 'tacet-vault27' },
|
|
2908
|
+
};
|
|
2909
|
+
const PASS27 = 'correct horse battery 27';
|
|
2910
|
+
try {
|
|
2911
|
+
// ① roundtrip 三資料形
|
|
2912
|
+
const c27 = await createVault(PASS27, CFG27);
|
|
2913
|
+
const v27 = c27.vault;
|
|
2914
|
+
const str27 = '默·日記 — 🌙 line\nbreak';
|
|
2915
|
+
const obj27 = { title: '標題', tags: ['a', 'b'], n: 3, ok: true, nil: null, nested: { x: [1, { y: 'z' }] } };
|
|
2916
|
+
const bytes27 = new Uint8Array([0, 1, 2, 254, 255, 0, 128]);
|
|
2917
|
+
const bS27 = await v27.encrypt('rec-s', str27);
|
|
2918
|
+
const bJ27 = await v27.encrypt('rec-j', obj27);
|
|
2919
|
+
const bB27 = await v27.encrypt('rec-b', bytes27);
|
|
2920
|
+
const bE27 = await v27.encrypt('rec-e', new Uint8Array(0));
|
|
2921
|
+
const bES27 = await v27.encrypt('rec-es', '');
|
|
2922
|
+
await A('[27] create→encrypt→decrypt roundtrip 三資料形(字串含 Unicode/JSON 巢狀物件/Uint8Array 含 0x00+空陣列+空字串)還原同型;blob 前綴=cfg.cipher;同明文兩次密文異(隨機 iv)',
|
|
2923
|
+
[bS27, bJ27, bB27, bE27, bES27].every((b) => b.startsWith('jr1v.'))
|
|
2924
|
+
&& (await v27.decrypt('rec-s', bS27)) === str27
|
|
2925
|
+
&& JSON.stringify(await v27.decrypt('rec-j', bJ27)) === JSON.stringify(obj27)
|
|
2926
|
+
&& bytesEq27(await v27.decrypt('rec-b', bB27), bytes27)
|
|
2927
|
+
&& bytesEq27(await v27.decrypt('rec-e', bE27), new Uint8Array(0))
|
|
2928
|
+
&& (await v27.decrypt('rec-es', bES27)) === ''
|
|
2929
|
+
&& (await v27.encrypt('rec-s', str27)) !== bS27);
|
|
2930
|
+
|
|
2931
|
+
// ② 寫面形+AAD 域(低階原語手解對帳)
|
|
2932
|
+
const rec27 = c27.serverRecord;
|
|
2933
|
+
const nk27 = await unwrapNoteKey4(CFG27.argon, rec27.wrapped, PASS27, rec27.salt);
|
|
2934
|
+
const openRaw27 = async (key: CryptoKey | null, blob: string, aad: string): Promise<Uint8Array | null> => {
|
|
2935
|
+
if (!key) return null;
|
|
2936
|
+
try {
|
|
2937
|
+
const p = unb64Mod(blob.slice(5));
|
|
2938
|
+
return new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv: p.slice(0, 12) as BufferSource, additionalData: enc.encode(aad) as BufferSource }, key, p.slice(12) as BufferSource));
|
|
2939
|
+
} catch { return null; }
|
|
2940
|
+
};
|
|
2941
|
+
const rawS27 = await openRaw27(nk27, bS27, 'tacet-vault27:rec-s');
|
|
2942
|
+
const kit27 = c27.recovery;
|
|
2943
|
+
await A('[27] 寫面形:serverRecord jr4w.+salt hex32、family wrap4、needsUpgrade false、carrier node;低階 unwrapNoteKey4 解得同鑰——AAD=`<app>:<recordId>`(tag 0x73+UTF-8 本體)、裸 recordId/他 app 域恆解不開;復原套件 recToken hex64+wrappedRec jr1r.+recTokenHash=SHA-256(recToken)',
|
|
2944
|
+
rec27.wrapped.startsWith('jr4w.') && /^[0-9a-f]{32}$/.test(rec27.salt) && v27.family === 'wrap4' && v27.needsUpgrade() === false && v27.carrier === 'node'
|
|
2945
|
+
&& !!rawS27 && rawS27[0] === 0x73 && new TextDecoder().decode(rawS27.subarray(1)) === str27
|
|
2946
|
+
&& (await openRaw27(nk27, bS27, 'rec-s')) === null && (await openRaw27(nk27, bS27, 'other-app:rec-s')) === null
|
|
2947
|
+
&& !!kit27 && /^[0-9a-f]{64}$/.test(kit27.recToken) && kit27.wrappedRec.startsWith('jr1r.') && kit27.recTokenHash === await sha(kit27.recToken));
|
|
2948
|
+
|
|
2949
|
+
// ③ unlockVault 新前綴
|
|
2950
|
+
const u27 = await unlockVault(PASS27, rec27, CFG27);
|
|
2951
|
+
await A('[27] unlockVault 新前綴(jr4w.):另一 handle 解前 handle 三資料形密文;family wrap4、needsUpgrade false',
|
|
2952
|
+
u27.family === 'wrap4' && !u27.needsUpgrade()
|
|
2953
|
+
&& (await u27.decrypt('rec-s', bS27)) === str27 && JSON.stringify(await u27.decrypt('rec-j', bJ27)) === JSON.stringify(obj27)
|
|
2954
|
+
&& bytesEq27(await u27.decrypt('rec-b', bB27), bytes27));
|
|
2955
|
+
|
|
2956
|
+
// ④ unlockVault 舊前綴(jr3w. Argon/jr1w. PBKDF2)——同一 noteKey 兩包
|
|
2957
|
+
const OLD27 = 'old pass 27';
|
|
2958
|
+
const nkOld27 = await generateNoteKey();
|
|
2959
|
+
const r3_27 = await wrapNoteKey3(CFG27.argon, nkOld27, OLD27);
|
|
2960
|
+
const r1_27 = await wrapNoteKey(TACET, nkOld27, OLD27);
|
|
2961
|
+
const u3_27 = await unlockVault(OLD27, r3_27, CFG27);
|
|
2962
|
+
const u1_27 = await unlockVault(OLD27, r1_27, CFG27);
|
|
2963
|
+
const bOld27 = await u3_27.encrypt('rec-old', 'old-era');
|
|
2964
|
+
await A('[27] unlockVault 舊前綴各一:jr3w.(family wrap3)/jr1w.(family wrap,PBKDF2 舊世代讀面=carrier null 不需 Argon)皆 needsUpgrade true;兩 handle 同鑰互解+低階 noteKey 手解同 AAD 域',
|
|
2965
|
+
u3_27.family === 'wrap3' && u1_27.family === 'wrap' && u3_27.needsUpgrade() && u1_27.needsUpgrade() && u1_27.carrier === null && u3_27.carrier === 'node'
|
|
2966
|
+
&& (await u1_27.decrypt('rec-old', bOld27)) === 'old-era'
|
|
2967
|
+
&& new TextDecoder().decode((await openRaw27(nkOld27, bOld27, 'tacet-vault27:rec-old'))?.subarray(1)) === 'old-era');
|
|
2968
|
+
|
|
2969
|
+
// ⑤ upgrade:錯密語拒(狀態不動)→ 正確密語重包 jr4w.;舊包裹仍可解(舊前綴語意不動);雙因子 jr3d. → jr4d.
|
|
2970
|
+
const upWrong27 = await codeOf27(u3_27.upgrade('not the pass'));
|
|
2971
|
+
const stillOld27 = u3_27.family === 'wrap3' && u3_27.serverRecord?.wrapped === r3_27.wrapped;
|
|
2972
|
+
const up27 = await u3_27.upgrade(OLD27);
|
|
2973
|
+
const uUp27 = await unlockVault(OLD27, up27, CFG27);
|
|
2974
|
+
const uOld27b = await unlockVault(OLD27, r3_27, CFG27);
|
|
2975
|
+
const upIdem27 = await u3_27.upgrade('ignored when current');
|
|
2976
|
+
const r3d27 = await wrapNoteKeyDual3(CFG27.argon, nkOld27, OLD27, '2468');
|
|
2977
|
+
const uD27 = await unlockVault(OLD27, r3d27, { ...CFG27, pin: '2468' });
|
|
2978
|
+
const dualBefore27 = fam27(uD27) === 'wrapDual3' && uD27.needsUpgrade(); // 升級前快照(斷言式在全部動作後求值)
|
|
2979
|
+
const upDualNoPin27 = await codeOf27(uD27.upgrade(OLD27));
|
|
2980
|
+
const upD27 = await uD27.upgrade(OLD27, { pin: '2468' });
|
|
2981
|
+
await A('[27] needsUpgrade/upgrade 真值行:錯密語=ERR_VAULT_WRAP_MISMATCH 且狀態不動;正確=新前綴 jr4w. 包同一 noteKey(新包裹解舊密文)、needsUpgrade 翻 false、已最新再呼=原包裹欄原樣;舊 jr3w. 包裹照解;jr3d. 缺 PIN=ERR_VAULT_PIN_REQUIRED、帶 PIN → jr4d.(family wrapDual4)',
|
|
2982
|
+
upWrong27 === 'ERR_VAULT_WRAP_MISMATCH' && stillOld27
|
|
2983
|
+
&& up27.wrapped.startsWith('jr4w.') && fam27(u3_27) === 'wrap4' && !u3_27.needsUpgrade()
|
|
2984
|
+
&& (await uUp27.decrypt('rec-old', bOld27)) === 'old-era' && (await uOld27b.decrypt('rec-old', bOld27)) === 'old-era'
|
|
2985
|
+
&& upIdem27.wrapped === up27.wrapped
|
|
2986
|
+
&& dualBefore27 && upDualNoPin27 === 'ERR_VAULT_PIN_REQUIRED'
|
|
2987
|
+
&& upD27.wrapped.startsWith('jr4d.') && fam27(uD27) === 'wrapDual4' && !uD27.needsUpgrade());
|
|
2988
|
+
|
|
2989
|
+
// ⑥ changePassphrase 週期(含 PIN 進出)
|
|
2990
|
+
const NEW27 = 'new pass 27';
|
|
2991
|
+
const cp27 = await v27.changePassphrase(NEW27);
|
|
2992
|
+
const uNew27 = await unlockVault(NEW27, cp27, CFG27);
|
|
2993
|
+
const oldOnNew27 = await codeOf27(unlockVault(PASS27, cp27, CFG27));
|
|
2994
|
+
const oldRecStill27 = await unlockVault(PASS27, rec27, CFG27);
|
|
2995
|
+
const cpD27 = await v27.changePassphrase(NEW27, { pin: '1357' });
|
|
2996
|
+
const famD27 = v27.family;
|
|
2997
|
+
const noPin27 = await codeOf27(unlockVault(NEW27, cpD27, CFG27));
|
|
2998
|
+
const uPin27 = await unlockVault(NEW27, cpD27, { ...CFG27, pin: '1357' });
|
|
2999
|
+
const cpNoPin27 = await codeOf27(v27.changePassphrase(NEW27));
|
|
3000
|
+
const cpBack27 = await v27.changePassphrase(NEW27, { pin: null });
|
|
3001
|
+
await A('[27] changePassphrase 週期:同一 noteKey 重包 jr4w.(新密語解舊密文、舊密語對新包裹=WRAP_MISMATCH、舊包裹欄不被本 API 觸及);pin 字串→jr4d.(無 PIN 解鎖=PIN_REQUIRED、帶 PIN 解);雙因子省略 pin=PIN_REQUIRED;pin:null 回 jr4w.;空密語=BAD_PASSPHRASE、空白 PIN=BAD_PIN',
|
|
3002
|
+
cp27.wrapped.startsWith('jr4w.') && cp27.wrapped !== rec27.wrapped && (await uNew27.decrypt('rec-s', bS27)) === str27
|
|
3003
|
+
&& oldOnNew27 === 'ERR_VAULT_WRAP_MISMATCH' && (await oldRecStill27.decrypt('rec-s', bS27)) === str27
|
|
3004
|
+
&& cpD27.wrapped.startsWith('jr4d.') && famD27 === 'wrapDual4' && noPin27 === 'ERR_VAULT_PIN_REQUIRED'
|
|
3005
|
+
&& (await uPin27.decrypt('rec-j', bJ27) as { title?: string }).title === '標題'
|
|
3006
|
+
&& cpNoPin27 === 'ERR_VAULT_PIN_REQUIRED' && cpBack27.wrapped.startsWith('jr4w.') && fam27(v27) === 'wrap4'
|
|
3007
|
+
&& (await codeOf27(v27.changePassphrase(''))) === 'ERR_VAULT_BAD_PASSPHRASE'
|
|
3008
|
+
&& (await codeOf27(v27.changePassphrase(NEW27, { pin: ' ' }))) === 'ERR_VAULT_BAD_PIN');
|
|
3009
|
+
|
|
3010
|
+
// ⑦ VaultError 毒化(記錄密文面)
|
|
3011
|
+
const tamper27 = (() => { const p = unb64Mod(bS27.slice(5)); p[p.length - 1] ^= 0x01; return 'jr1v.' + modB64(p); })();
|
|
3012
|
+
const errObj27 = await (async () => { try { await v27.decrypt('rec-OTHER', bS27); return null; } catch (e) { return e; } })();
|
|
3013
|
+
const cyc27: Record<string, unknown> = {}; cyc27.self = cyc27;
|
|
3014
|
+
await A('[27] VaultError 毒化(記錄面):錯 recordId(AAD 搬列)/末位元翻轉竄改/他 vault 金鑰=ERR_VAULT_DECRYPT;他前綴/壞 base64/過短=ERR_VAULT_BAD_BLOB;空 recordId=BAD_RECORD_ID;undefined/ArrayBuffer/Uint16Array/BigInt/循環物件=BAD_DATA;錯誤物件 instanceof VaultError+name+isVaultError 分流',
|
|
3015
|
+
errObj27 instanceof VaultError && (errObj27 as VaultError).name === 'VaultError' && isVaultError(errObj27, 'ERR_VAULT_DECRYPT') && !isVaultError(errObj27, 'ERR_VAULT_BAD_BLOB')
|
|
3016
|
+
&& (await codeOf27(v27.decrypt('rec-s', tamper27))) === 'ERR_VAULT_DECRYPT'
|
|
3017
|
+
&& (await codeOf27(u3_27.decrypt('rec-s', bS27))) === 'ERR_VAULT_DECRYPT'
|
|
3018
|
+
&& (await codeOf27(v27.decrypt('rec-s', 'jr1b.' + bS27.slice(5)))) === 'ERR_VAULT_BAD_BLOB'
|
|
3019
|
+
&& (await codeOf27(v27.decrypt('rec-s', 'jr1v.!!not-base64!!'))) === 'ERR_VAULT_BAD_BLOB'
|
|
3020
|
+
&& (await codeOf27(v27.decrypt('rec-s', 'jr1v.' + modB64(new Uint8Array(20))))) === 'ERR_VAULT_BAD_BLOB'
|
|
3021
|
+
&& (await codeOf27(v27.encrypt('', 'x'))) === 'ERR_VAULT_BAD_RECORD_ID'
|
|
3022
|
+
&& (await codeOf27(v27.decrypt('', bS27))) === 'ERR_VAULT_BAD_RECORD_ID'
|
|
3023
|
+
&& (await codeOf27(v27.encrypt('r', undefined as never))) === 'ERR_VAULT_BAD_DATA'
|
|
3024
|
+
&& (await codeOf27(v27.encrypt('r', new ArrayBuffer(4) as never))) === 'ERR_VAULT_BAD_DATA'
|
|
3025
|
+
&& (await codeOf27(v27.encrypt('r', new Uint16Array(2) as never))) === 'ERR_VAULT_BAD_DATA'
|
|
3026
|
+
&& (await codeOf27(v27.encrypt('r', 10n as never))) === 'ERR_VAULT_BAD_DATA'
|
|
3027
|
+
&& (await codeOf27(v27.encrypt('r', cyc27 as never))) === 'ERR_VAULT_BAD_DATA');
|
|
3028
|
+
|
|
3029
|
+
// ⑧ 配置/serverRecord 拒(opt-in 未配置即拒、不猜)
|
|
3030
|
+
const noW4_27 = { ...CFG27, argon: { ...CFG27.argon, wrap4: undefined } };
|
|
3031
|
+
await A('[27] VaultError 拒(配置/包裹欄面):serverRecord 缺 salt/缺 wrapped/salt 大寫/null=BAD_RECORD;未知前綴=WRAP_UNKNOWN;缺 argon.wrap4/缺 app/缺 cipher/前綴互為前綴/recovery 半配置/PIN 寫面未配置=ERR_VAULT_CONFIG;空密語=BAD_PASSPHRASE',
|
|
3032
|
+
(await codeOf27(unlockVault(PASS27, { wrapped: rec27.wrapped } as never, CFG27))) === 'ERR_VAULT_BAD_RECORD'
|
|
3033
|
+
&& (await codeOf27(unlockVault(PASS27, { salt: rec27.salt } as never, CFG27))) === 'ERR_VAULT_BAD_RECORD'
|
|
3034
|
+
&& (await codeOf27(unlockVault(PASS27, { wrapped: rec27.wrapped, salt: rec27.salt.toUpperCase() }, CFG27))) === 'ERR_VAULT_BAD_RECORD'
|
|
3035
|
+
&& (await codeOf27(unlockVault(PASS27, null as never, CFG27))) === 'ERR_VAULT_BAD_RECORD'
|
|
3036
|
+
&& (await codeOf27(unlockVault(PASS27, { wrapped: 'jr9w.' + rec27.wrapped.slice(5), salt: rec27.salt }, CFG27))) === 'ERR_VAULT_WRAP_UNKNOWN'
|
|
3037
|
+
&& (await codeOf27(createVault(PASS27, noW4_27))) === 'ERR_VAULT_CONFIG'
|
|
3038
|
+
&& (await codeOf27(createVault(PASS27, { ...CFG27, app: '' }))) === 'ERR_VAULT_CONFIG'
|
|
3039
|
+
&& (await codeOf27(createVault(PASS27, { ...CFG27, cipher: '' }))) === 'ERR_VAULT_CONFIG'
|
|
3040
|
+
&& (await codeOf27(createVault(PASS27, { ...CFG27, cipher: 'jr4' }))) === 'ERR_VAULT_CONFIG'
|
|
3041
|
+
&& (await codeOf27(createVault(PASS27, { ...CFG27, recovery: { wrapRec: 'jr1r.', recKekHkdf: '' } }))) === 'ERR_VAULT_CONFIG'
|
|
3042
|
+
&& (await codeOf27(createVault(PASS27, { ...CFG27, argon: { wrap4: 'jr4w.' }, pin: '1234' }))) === 'ERR_VAULT_CONFIG'
|
|
3043
|
+
&& (await codeOf27(createVault('', CFG27))) === 'ERR_VAULT_BAD_PASSPHRASE');
|
|
3044
|
+
|
|
3045
|
+
// ⑨ 復原套件
|
|
3046
|
+
const rv27 = await recoverVault(kit27!.recToken, kit27!.wrappedRec, CFG27);
|
|
3047
|
+
const rvFamNull27 = rv27.family === null && rv27.serverRecord === null && rv27.carrier === null && !rv27.needsUpgrade();
|
|
3048
|
+
const rvUp27 = await codeOf27(rv27.upgrade(PASS27));
|
|
3049
|
+
const rvCp27 = await rv27.changePassphrase('recovered 27');
|
|
3050
|
+
const rvU27 = await unlockVault('recovered 27', rvCp27, CFG27);
|
|
3051
|
+
const wrongTok27 = (kit27!.recToken[0] === 'a' ? 'b' : 'a') + kit27!.recToken.slice(1);
|
|
3052
|
+
await A('[27] 復原套件:recoverVault(recToken, wrappedRec) 解舊密文;family/serverRecord/carrier 皆 null(HKDF 不需 Argon)、upgrade=BAD_RECORD、changePassphrase 設新密語後 unlock 可解;錯 recToken/非 hex64/他前綴=ERR_VAULT_RECOVERY;未配置 recovery=ERR_VAULT_CONFIG',
|
|
3053
|
+
(await rv27.decrypt('rec-s', bS27)) === str27 && rvFamNull27 && rvUp27 === 'ERR_VAULT_BAD_RECORD'
|
|
3054
|
+
&& rvCp27.wrapped.startsWith('jr4w.') && (await rvU27.decrypt('rec-b', bB27) as Uint8Array).length === bytes27.length
|
|
3055
|
+
&& (await codeOf27(recoverVault(wrongTok27, kit27!.wrappedRec, CFG27))) === 'ERR_VAULT_RECOVERY'
|
|
3056
|
+
&& (await codeOf27(recoverVault('xyz', kit27!.wrappedRec, CFG27))) === 'ERR_VAULT_RECOVERY'
|
|
3057
|
+
&& (await codeOf27(recoverVault(kit27!.recToken, 'jr1w.' + kit27!.wrappedRec.slice(5), CFG27))) === 'ERR_VAULT_RECOVERY'
|
|
3058
|
+
&& (await codeOf27(recoverVault(kit27!.recToken, kit27!.wrappedRec, { ...CFG27, recovery: undefined }))) === 'ERR_VAULT_CONFIG');
|
|
3059
|
+
|
|
3060
|
+
// ⑩ 載體覆寫顯形(setArgonLoader 注入恆優先;錯值載體 KAT 拒用)
|
|
3061
|
+
let cnt27 = 0;
|
|
3062
|
+
const fac27 = hashWasmArgon2Factory();
|
|
3063
|
+
const countLoader27 = () => { cnt27++; return Promise.resolve(fac27); };
|
|
3064
|
+
setArgonLoader(countLoader27);
|
|
3065
|
+
const cInj27 = await createVault(PASS27, { ...CFG27, recovery: undefined });
|
|
3066
|
+
const injKind27 = cInj27.vault.carrier;
|
|
3067
|
+
const injCur27 = currentArgonCarrier();
|
|
3068
|
+
const uFromNode27 = await unlockVault(PASS27, rec27, CFG27); // 注入載體解 node 載體所寫
|
|
3069
|
+
setArgonLoader(null);
|
|
3070
|
+
const uFromInj27 = await unlockVault(PASS27, cInj27.serverRecord, CFG27); // node 載體解注入載體所寫
|
|
3071
|
+
const zeroLoader27 = () => Promise.resolve({ argon2id: (async () => new Uint8Array(32)) as never });
|
|
3072
|
+
setArgonLoader(zeroLoader27);
|
|
3073
|
+
const badKat27 = await codeOf27(createVault(PASS27, CFG27));
|
|
3074
|
+
setArgonLoader(null);
|
|
3075
|
+
await A('[27] 載體覆寫顯形:setArgonLoader 注入=carrier/currentArgonCarrier 皆 injected 且注入載體實被呼叫;注入↔node 互解(逐位元一致);撤注入回 node;錯值載體(恆零輸出)=RFC 9106 KAT 拒用 ERR_VAULT_KDF_UNSUPPORTED(zero-fallback:不降級)',
|
|
3076
|
+
injKind27 === 'injected' && injCur27 === 'injected' && cnt27 > 0
|
|
3077
|
+
&& (await uFromNode27.decrypt('rec-s', bS27)) === str27 && uFromInj27.family === 'wrap4' && uFromInj27.carrier === 'node'
|
|
3078
|
+
&& currentArgonCarrier() === 'node' && badKat27 === 'ERR_VAULT_KDF_UNSUPPORTED');
|
|
3079
|
+
|
|
3080
|
+
// ⑪ 真 Worker 載體(node worker_threads 轉接層跑 argonWorkerSource 原文)
|
|
3081
|
+
const wt27 = (globalThis as unknown as { process?: { getBuiltinModule?: (id: string) => unknown } }).process?.getBuiltinModule?.('node:worker_threads') as
|
|
3082
|
+
{ Worker?: new (src: string, o: { eval: boolean }) => { postMessage(m: unknown, t?: unknown): void; terminate(): Promise<number>; on(ev: string, f: (x: unknown) => void): void } } | undefined;
|
|
3083
|
+
if (!wt27?.Worker) {
|
|
3084
|
+
await A('[27] 真 Worker 載體(worker_threads 缺席環境)=顯性 FAIL 行(非 silent true)', false, 'node:worker_threads 缺席');
|
|
3085
|
+
} else {
|
|
3086
|
+
const SHIM27 = "const { parentPort } = require('node:worker_threads'); globalThis.self = { postMessage: (m, t) => parentPort.postMessage(m, t), set onmessage(f) { parentPort.on('message', (d) => f({ data: d })); } };\n";
|
|
3087
|
+
let made27 = 0;
|
|
3088
|
+
const nodeWorker27 = (): ArgonWorkerLike => {
|
|
3089
|
+
made27++;
|
|
3090
|
+
const w = new wt27.Worker!(SHIM27 + argonWorkerSource(), { eval: true });
|
|
3091
|
+
const like: ArgonWorkerLike = { postMessage: (m, t) => w.postMessage(m, t), terminate: () => { void w.terminate(); }, onmessage: null, onerror: null };
|
|
3092
|
+
w.on('message', (d) => like.onmessage?.({ data: d }));
|
|
3093
|
+
w.on('error', (e) => like.onerror?.(e));
|
|
3094
|
+
return like;
|
|
3095
|
+
};
|
|
3096
|
+
const wOpts27 = { chain: ['worker', 'inline'] as const, createWorker: nodeWorker27 };
|
|
3097
|
+
const cW27 = await createVault(PASS27, { ...CFG27, recovery: undefined, carrier: wOpts27 });
|
|
3098
|
+
const wKind27 = cW27.vault.carrier;
|
|
3099
|
+
const wCur27 = currentArgonCarrier();
|
|
3100
|
+
const uW27 = await unlockVault(PASS27, rec27, { ...CFG27, carrier: wOpts27 }); // Worker 解 node 所寫
|
|
3101
|
+
resetArgonCarrier();
|
|
3102
|
+
const afterReset27 = currentArgonCarrier();
|
|
3103
|
+
const uWn27 = await unlockVault(PASS27, cW27.serverRecord, CFG27); // node 解 Worker 所寫
|
|
3104
|
+
await A('[27] 真 Worker 載體:chain [worker,inline]+worker_threads 轉接 argonWorkerSource 原文=carrier worker(單一 Worker 重用);Worker↔node 互解逐位元一致;resetArgonCarrier 撤自裝載體回 node',
|
|
3105
|
+
wKind27 === 'worker' && wCur27 === 'worker' && made27 === 1 && uW27.carrier === 'worker'
|
|
3106
|
+
&& (await uW27.decrypt('rec-s', bS27)) === str27 && uWn27.family === 'wrap4' && uWn27.carrier === 'node' && afterReset27 === 'node');
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
// ⑫ 回退鏈+失效歸因
|
|
3110
|
+
const events27: { from: string; to: string }[] = [];
|
|
3111
|
+
const broken27 = (): ArgonWorkerLike => { throw new Error('CSP worker-src blocked (simulated)'); };
|
|
3112
|
+
const fb27 = await unlockVault(PASS27, rec27, { ...CFG27, carrier: { chain: ['worker', 'inline'], createWorker: broken27, onFallback: (e) => events27.push(e) } });
|
|
3113
|
+
const fbKind27 = fb27.carrier;
|
|
3114
|
+
const fbCur27 = currentArgonCarrier();
|
|
3115
|
+
resetArgonCarrier();
|
|
3116
|
+
const noFb27 = await codeOf27(unlockVault(PASS27, rec27, { ...CFG27, carrier: { chain: ['worker'], createWorker: broken27 } }));
|
|
3117
|
+
resetArgonCarrier();
|
|
3118
|
+
// 健康 KAT(32 KiB)、64 MiB 派生即 ok:false 的 flaky Worker:無回退=KDF_UNSUPPORTED(非 WRAP_MISMATCH);有回退=成功+事件
|
|
3119
|
+
const flaky27 = (): ArgonWorkerLike => {
|
|
3120
|
+
const fac = hashWasmArgon2Factory();
|
|
3121
|
+
const like: ArgonWorkerLike = {
|
|
3122
|
+
postMessage: (m) => {
|
|
3123
|
+
const q = m as { id: number; memorySize: number; password: string | Uint8Array; salt: Uint8Array; iterations: number; parallelism: number; hashLength: number };
|
|
3124
|
+
void (async () => {
|
|
3125
|
+
if (q.memorySize > 1024) { like.onmessage?.({ data: { id: q.id, ok: false, error: 'OOM (simulated)' } }); return; }
|
|
3126
|
+
const out = await fac.argon2id({ ...q, outputType: 'binary' }) as Uint8Array;
|
|
3127
|
+
like.onmessage?.({ data: { id: q.id, ok: true, out } });
|
|
3128
|
+
})();
|
|
3129
|
+
},
|
|
3130
|
+
terminate: () => {},
|
|
3131
|
+
onmessage: null,
|
|
3132
|
+
onerror: null,
|
|
3133
|
+
};
|
|
3134
|
+
return like;
|
|
3135
|
+
};
|
|
3136
|
+
const attrib27 = await codeOf27(unlockVault(PASS27, rec27, { ...CFG27, carrier: { chain: ['worker'], createWorker: flaky27 } }));
|
|
3137
|
+
resetArgonCarrier();
|
|
3138
|
+
const events27b: unknown[] = [];
|
|
3139
|
+
const fl27 = await unlockVault(PASS27, rec27, { ...CFG27, carrier: { chain: ['worker', 'inline'], createWorker: flaky27, onFallback: (e) => events27b.push(e) } });
|
|
3140
|
+
const flKind27 = fl27.carrier === 'worker' ? currentArgonCarrier() : fl27.carrier;
|
|
3141
|
+
resetArgonCarrier();
|
|
3142
|
+
const absent27 = await codeOf27(unlockVault(PASS27, rec27, { ...CFG27, carrier: { chain: ['worker'] } })); // node 無全域 Worker=載體缺席
|
|
3143
|
+
const emptyChain27 = await codeOf27(createVault(PASS27, { ...CFG27, carrier: { chain: [] } }));
|
|
3144
|
+
resetArgonCarrier();
|
|
3145
|
+
await A('[27] 回退鏈顯形:Worker 建構失敗→inline(onFallback 1 次 worker→inline、carrier 翻 inline);chain 無 inline=ERR_VAULT_KDF_UNSUPPORTED;派生期 Worker 失效(KAT 過、64 MiB 失敗)無回退=KDF_UNSUPPORTED 不誤報 WRAP_MISMATCH、有回退=成功+事件;載體缺席(node 無全域 Worker、chain [worker])=KDF_UNSUPPORTED;空 chain=CONFIG',
|
|
3146
|
+
fbKind27 === 'inline' && fbCur27 === 'inline' && events27.length === 1 && events27[0].from === 'worker' && events27[0].to === 'inline'
|
|
3147
|
+
&& noFb27 === 'ERR_VAULT_KDF_UNSUPPORTED'
|
|
3148
|
+
&& attrib27 === 'ERR_VAULT_KDF_UNSUPPORTED'
|
|
3149
|
+
&& (await fl27.decrypt('rec-s', bS27)) === str27 && flKind27 === 'inline' && events27b.length === 1
|
|
3150
|
+
&& absent27 === 'ERR_VAULT_KDF_UNSUPPORTED' && emptyChain27 === 'ERR_VAULT_CONFIG');
|
|
3151
|
+
} catch (e) {
|
|
3152
|
+
await A('[27] 行為面未預期拋出(應全數經 VaultError 分流或斷言承載)', false, String((e as Error)?.stack ?? e).slice(0, 400));
|
|
3153
|
+
} finally {
|
|
3154
|
+
resetArgonCarrier();
|
|
3155
|
+
setArgonLoader(null);
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
// ⑬ vendor 孿生+零 runtime deps
|
|
3159
|
+
const vendSrc27 = await srcOf('../src/client/vendor/hash-wasm-argon2.ts');
|
|
3160
|
+
const vb27 = vendSrc27.indexOf(' // VENDOR-BEGIN\n');
|
|
3161
|
+
const ve27 = vendSrc27.indexOf(' // VENDOR-END\n');
|
|
3162
|
+
const restored27 = vb27 > 0 && ve27 > vb27
|
|
3163
|
+
? vendSrc27.slice(vb27 + ' // VENDOR-BEGIN\n'.length, ve27).split('\n').map((l) => (l.startsWith(' ') ? l.slice(2) : l)).join('\n')
|
|
3164
|
+
: '';
|
|
3165
|
+
const HASH_WASM_PIN27 = 'dcec617a2e1b700fa132d1583a186cb70611113395e869f2dd6cc82b415d3094'; // hash-wasm@4.12.0 dist/argon2.umd.min.js
|
|
3166
|
+
const kat27 = await hashWasmArgon2Factory().argon2id({ password: new Uint8Array(32).fill(1), salt: new Uint8Array(16), iterations: 3, parallelism: 4, memorySize: 32, hashLength: 32, outputType: 'hex' });
|
|
3167
|
+
const pkg27 = JSON.parse(await srcOf('../package.json')) as { dependencies?: unknown; devDependencies?: Record<string, string>; files?: string[]; exports?: Record<string, { types?: string; default?: string }> };
|
|
3168
|
+
await A('[27] vendor 孿生:去兩格縮排原文 sha256=HASH_WASM_ARGON2_SHA256=閘釘選值(hash-wasm@4.12.0);vendor inline 載體 RFC 9106 KAT 一致且零全域寫入;零 runtime deps(無 dependencies 欄)+hash-wasm 僅 devDependencies+產生器 tarball 排除',
|
|
3169
|
+
restored27.length > 20000 && await sha(restored27) === HASH_WASM_ARGON2_SHA256 && HASH_WASM_ARGON2_SHA256 === HASH_WASM_PIN27 && HASH_WASM_VERSION === '4.12.0'
|
|
3170
|
+
&& kat27 === ARGON_RFC9106_EXPECTED && (globalThis as unknown as { hashwasm?: unknown }).hashwasm === undefined
|
|
3171
|
+
&& pkg27.dependencies === undefined && typeof pkg27.devDependencies?.['hash-wasm'] === 'string'
|
|
3172
|
+
&& !!pkg27.files?.includes('!scripts/vendor-hash-wasm.cjs'));
|
|
3173
|
+
|
|
3174
|
+
// ⑭ exports 子路徑+分層單向+靜態錨
|
|
3175
|
+
const vaultSrc27 = await srcOf('../src/client/vault.ts');
|
|
3176
|
+
const autoSrc27 = await srcOf('../src/client/argon-auto.ts');
|
|
3177
|
+
const lowSrcs27 = [await srcOf('../src/client/note-crypto.ts'), await srcOf('../src/client/argon2.ts')];
|
|
3178
|
+
await A('[27] exports `./client/vault` 指 dist(types .d.ts+default .js 雙形);分層單向(note-crypto.ts/argon2.ts 零 import vault/argon-auto/argon-worker);AAD 構形錨 `app + \':\' + recordId` 恰 1;argon-auto 零 PBKDF2 碼面(無 \'PBKDF2\' 字面、無 note-crypto import——zero-fallback 錨)',
|
|
3179
|
+
pkg27.exports?.['./client/vault']?.types === './dist/client/vault.d.ts' && pkg27.exports?.['./client/vault']?.default === './dist/client/vault.js'
|
|
3180
|
+
&& lowSrcs27.every((s) => !/from '\.\/(vault|argon-auto|argon-worker)/.test(s))
|
|
3181
|
+
&& vaultSrc27.includes('new TextEncoder().encode(app + \':\' + recordId)')
|
|
3182
|
+
&& vaultSrc27.split('TextEncoder().encode(app + \':\' + recordId)').length - 1 === 1
|
|
3183
|
+
&& !/['"]PBKDF2['"]|from '\.\/note-crypto/.test(autoSrc27));
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
|
|
2792
3187
|
secOpen(24, '[24] runner 契約自證(--only 解析契約 fail-closed+段級帳面顯形)'); if (secEnter(24)) {
|
|
2793
3188
|
await A('[24] --only 解析契約(全跑態):spec 缺席=onlySet 空集=secEnter 恆真(CI 形契約不變——npm run verify 全帳;--only 態本面恆真跳過=帳面由次面承載)',
|
|
2794
3189
|
!onlyMode() ? (onlySpecRaw === null && parseOnly('').err === 'ERR_ONLY_SPEC_EMPTY' && secEnter(1) && secEnter(23)) : true);
|
|
@@ -2809,19 +3204,19 @@ secOpen(24, '[24] runner 契約自證(--only 解析契約 fail-closed+段級
|
|
|
2809
3204
|
}
|
|
2810
3205
|
|
|
2811
3206
|
secOpen(25, '[25] 段級運行樣本:secEnter 真值表+tag 形帳+secCases 段帳'); if (secEnter(25)) {
|
|
2812
|
-
await A('[25] secEnter 真值表:bannerless 段全跑態行為(
|
|
2813
|
-
!onlyMode() ? (secEnter(0) === false && (secRan.get(
|
|
3207
|
+
await A('[25] secEnter 真值表:bannerless 段全跑態行為(99 非段位=不在 secOrder 段帳=secRan 零位面——帳面逐段只列 secOrder;n=0 恆 false;原哨兵 27 隨 A3 [27] 實段落地改 99);全跑態實段 1/15/23 全真',
|
|
3208
|
+
!onlyMode() ? (secEnter(0) === false && (secRan.get(99) ?? 0) === 0 && secEnter(1) && secEnter(15) && secEnter(23)) : true);
|
|
2814
3209
|
await A('[25] tag/spec 形帳:--only 態 (--ONLY …) 括形+spec 遞增 CSV 非空;全跑態 spec 空+tag 裸',
|
|
2815
3210
|
onlyMode() ? (onlyTag().startsWith('(--ONLY ') && onlyTag().endsWith(')') && onlySpecDisp().length > 0)
|
|
2816
3211
|
: (onlySpecDisp() === '' && onlyTag() === ''));
|
|
2817
3212
|
await A('[25] secCases 段帳登記義務(負向斷言——teeth):secRan 實跑段(本機樹 [23]⑤ fail-open 表缺席異常面照行)必已登記;secCases 缺鍵且非 22 段=RED(secCases 全摘禁令的行為面承載)',
|
|
2818
3213
|
(() => { for (const [k25] of secRan) if (!secCases[k25] && k25 !== 22) return false; return true; })());
|
|
2819
|
-
await A('[25] self-account:secCases[25] =
|
|
2820
|
-
secCases[25] ===
|
|
3214
|
+
await A('[25] self-account:secCases[25] = 4 恰此四案(段帳面 self-proving——帳漂移即本面 RED;secCases 載體面:[24]/[25] 自證段+--only 段存在面+teeth 三載體)',
|
|
3215
|
+
secCases[25] === 4 && (secRan.get(25) ?? 0) === 3);
|
|
2821
3216
|
}
|
|
2822
3217
|
|
|
2823
3218
|
// ── 段級帳面(誠實帳——skip 段恆列帳「未跑非通過」;全跑態逐段案數對 secCases 帳面)──
|
|
2824
|
-
const secOrder = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25];
|
|
3219
|
+
const secOrder = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27];
|
|
2825
3220
|
const secTotal = secOrder.reduce((a, n) => a + (secCases[n] ?? 0), 0);
|
|
2826
3221
|
const ranSecs = secOrder.filter((n) => (secRan.get(n) ?? 0) > 0);
|
|
2827
3222
|
const selTotal = secOrder.filter((n) => !onlyMode() || onlySet.has(n) || n === 24 || n === 25).reduce((a, n) => a + (secCases[n] ?? 0), 0);
|