@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/src/client/bip39.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* bip39.ts — 復原套件轉寫層(BIP39 英文 24 詞 ⇄ entropy 32B ⇄ recToken hex64)。
|
|
3
|
-
*
|
|
4
|
-
* 契約裁定(2026-09-10 復原套件三件):
|
|
5
|
-
* - **轉寫層,非 KDF 升級**:24 詞 ⇄ hex64 可逆互轉;線上契約(rec_hash = SHA-256(hex64)、
|
|
6
|
-
* wrappedRec = jr1w. + GCM(PBKDF2(recToken, ...)))完全不變,伺服器零改動。
|
|
7
|
-
* 熵等價:24 詞 = 256-bit entropy + 8-bit checksum(BIP39 標準);與舊 hex64 同熵。
|
|
8
|
-
* - **checksum 是轉寫品質閘**:單詞打錯 255/256 機率當場被抓(64 hex 轉寫無此偵測);
|
|
9
|
-
* 解析失敗一律回 null(不洩漏哪類錯:字數/詞表外/checksum 統一,UI 統一訊息)。
|
|
10
|
-
* - **只收 24 詞**:12 詞是降級禁用(security-roadmap 裁定);15/18/21 不收(契約面單一)。
|
|
11
|
-
* - **相容層永久**:舊帳戶 hex64 紙本照走現行鏈;recTokenToWords 供既有套件顯示用。
|
|
12
|
-
* - **零依賴**:SHA-256 用 WebCrypto(全 async API);bits 打包自製(閘 [9] 以
|
|
13
|
-
* @scure/bip39 參照實作 200 組雙向對照守證)。禁用 @scure 的 mnemonicToSeed
|
|
14
|
-
* (PBKDF2-SHA512 seed 派生與 Tacet 契約無關,不可混入)。
|
|
15
|
-
*/
|
|
16
|
-
import { wordlist } from './wordlist.ts';
|
|
17
|
-
|
|
18
|
-
const HEX64_RE = /^[0-9a-f]{64}$/;
|
|
19
|
-
|
|
20
|
-
async function sha256(data: Uint8Array): Promise<Uint8Array> {
|
|
21
|
-
return new Uint8Array(await crypto.subtle.digest('SHA-256', data as BufferSource));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function hexToBytes(hexStr: string): Uint8Array {
|
|
25
|
-
const out = new Uint8Array(hexStr.length / 2);
|
|
26
|
-
for (let i = 0; i < out.length; i++) out[i] = parseInt(hexStr.slice(i * 2, i * 2 + 2), 16);
|
|
27
|
-
return out;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function bytesToHex(bytes: Uint8Array): string {
|
|
31
|
-
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** 輸入正規化:NFKC → 換行/全形空白/多空白收斂單半形空格 → 小寫(wordlist 全小寫,輸入容錯)。 */
|
|
35
|
-
function normalizeWords(input: string): string[] {
|
|
36
|
-
return input.normalize('NFKC').trim().toLowerCase().split(/\s+/).filter(w => w.length > 0);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** 11-bit 打包(BIP39 標準位元累積器;carry 恆 < 19 bits,索引 = 低位遮罩 0x7ff)。 */
|
|
40
|
-
function entropyToIndexes(entropy: Uint8Array, checksum: number): number[] {
|
|
41
|
-
const bytes = new Uint8Array(entropy.length + 1);
|
|
42
|
-
bytes.set(entropy);
|
|
43
|
-
bytes[entropy.length] = checksum; // 32B entropy → checksum 8 bits = SHA-256 首位元組全值
|
|
44
|
-
const indexes: number[] = [];
|
|
45
|
-
let carry = 0;
|
|
46
|
-
let bits = 0;
|
|
47
|
-
for (const byte of bytes) {
|
|
48
|
-
carry = (carry << 8) | byte;
|
|
49
|
-
bits += 8;
|
|
50
|
-
if (bits >= 11) {
|
|
51
|
-
bits -= 11;
|
|
52
|
-
indexes.push((carry >>> bits) & 0x7ff);
|
|
53
|
-
carry &= (1 << bits) - 1;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return indexes;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** 11-bit 逆打包:24 個索引 → 33 bytes(entropy 32B + checksum 1B)。 */
|
|
60
|
-
function indexesToBytes(indexes: number[]): Uint8Array {
|
|
61
|
-
const out = new Uint8Array(33);
|
|
62
|
-
let carry = 0;
|
|
63
|
-
let bits = 0;
|
|
64
|
-
let pos = 0;
|
|
65
|
-
for (const idx of indexes) {
|
|
66
|
-
carry = (carry << 11) | idx;
|
|
67
|
-
bits += 11;
|
|
68
|
-
while (bits >= 8) {
|
|
69
|
-
bits -= 8;
|
|
70
|
-
out[pos++] = (carry >>> bits) & 0xff;
|
|
71
|
-
carry &= (1 << bits) - 1;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return out; // 24 詞 = 264 bits = 33 bytes 整除,無餘位(11-bit 打包自洽)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** 產生 24 詞套件(entropy 32B 隨機 → +8-bit checksum → 24 詞)。
|
|
78
|
-
* entropyBytes 可注入(驗證閘固定種子樣本用;產品面不傳=真隨機契約不變)。 */
|
|
79
|
-
export async function generateBip39Words(entropyBytes?: Uint8Array): Promise<string[]> {
|
|
80
|
-
const entropy = entropyBytes ?? crypto.getRandomValues(new Uint8Array(32));
|
|
81
|
-
const checksum = (await sha256(entropy))[0];
|
|
82
|
-
return entropyToIndexes(entropy, checksum).map(idx => wordlist[idx]);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ── 驗證閘固定種子樣本(flake 歸零)──────────────────────────────
|
|
86
|
-
//
|
|
87
|
-
// 背景:checksum 統計面「錯一詞 64 樣本 ≥63 被抓」用真隨機取樣時,P(漏 ≥2)≈0.4%,
|
|
88
|
-
// 偶發 62/64=閘 flake(2026-09-19 session 實證)。治本是固定種子:xorshift32 以
|
|
89
|
-
// 常數 seed 產生確定性位元組流(種子決定樣本集=向量可重放、閘輸出逐輪恆定)。
|
|
90
|
-
// 樣本集僅供驗證閘使用(checksum 是 BIP39 公開數學,非機密;產品面
|
|
91
|
-
// generateBip39Words() 不帶參數=crypto.getRandomValues 真隨機契約不變)。
|
|
92
|
-
|
|
93
|
-
function xorshift32Bytes(seed: number, len: number): Uint8Array {
|
|
94
|
-
let s = seed >>> 0;
|
|
95
|
-
if (s === 0) s = 0x9e3779b9; // xorshift 全零態死鎖防護
|
|
96
|
-
const out = new Uint8Array(len);
|
|
97
|
-
for (let i = 0; i < len; i++) {
|
|
98
|
-
s ^= s << 13; s >>>= 0;
|
|
99
|
-
s ^= s >>> 17;
|
|
100
|
-
s ^= s << 5; s >>>= 0;
|
|
101
|
-
out[i] = s & 0xff;
|
|
102
|
-
}
|
|
103
|
-
return out;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** 固定種子樣本集:seed 給定 → 32B 確定(xorshift32 常數種子流)。 */
|
|
107
|
-
export function seededSampleBytes(seed: number): Uint8Array {
|
|
108
|
-
return xorshift32Bytes(seed, 32);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** 24 詞 → recToken hex64。任何不符(字數/詞表外/checksum)回 null,不洩漏哪類錯。 */
|
|
112
|
-
export async function wordsToRecToken(input: string | string[]): Promise<string | null> {
|
|
113
|
-
const words = typeof input === 'string' ? normalizeWords(input) : input.map(w => w.normalize('NFKC').trim().toLowerCase());
|
|
114
|
-
if (words.length !== 24) return null;
|
|
115
|
-
const indexes: number[] = [];
|
|
116
|
-
for (const w of words) {
|
|
117
|
-
const idx = wordlist.indexOf(w);
|
|
118
|
-
if (idx < 0) return null;
|
|
119
|
-
indexes.push(idx);
|
|
120
|
-
}
|
|
121
|
-
const bytes = indexesToBytes(indexes);
|
|
122
|
-
const entropy = bytes.slice(0, 32);
|
|
123
|
-
const checksum = bytes[32];
|
|
124
|
-
const expected = (await sha256(entropy))[0];
|
|
125
|
-
if (checksum !== expected) return null;
|
|
126
|
-
return bytesToHex(entropy);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** recToken hex64 → 24 詞(既有套件顯示需求;非 hex64 或格式不符回 null)。 */
|
|
130
|
-
export async function recTokenToWords(recToken: string): Promise<string[] | null> {
|
|
131
|
-
if (!HEX64_RE.test(recToken)) return null;
|
|
132
|
-
const entropy = hexToBytes(recToken);
|
|
133
|
-
const checksum = (await sha256(entropy))[0];
|
|
134
|
-
return entropyToIndexes(entropy, checksum).map(idx => wordlist[idx]);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** 抄寫抽驗位置:24 詞中抽 4 個不重複位置(0-23),排序回傳(顯示順序穩定)。 */
|
|
138
|
-
export function spotCheckIndexes(): number[] {
|
|
139
|
-
const pool = Array.from({ length: 24 }, (_, i) => i);
|
|
140
|
-
for (let i = pool.length - 1; i > 0; i--) {
|
|
141
|
-
const j = crypto.getRandomValues(new Uint32Array(1))[0] % (i + 1);
|
|
142
|
-
[pool[i], pool[j]] = [pool[j], pool[i]];
|
|
143
|
-
}
|
|
144
|
-
return pool.slice(0, 4).sort((a, b) => a - b);
|
|
145
|
-
}
|
package/src/client/keys.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* keys.ts — storage key 命名空間。各產品以 config 指定前綴(如 journal 的 <brand>_)。
|
|
3
|
-
* 理由:fork diff 證實 noteCrypto 差異一半在 localStorage key 前綴——參數化收進 core。
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface KeyStoreConfig {
|
|
7
|
-
/** 產品前綴,如 'tacet'(不含底線)。 */
|
|
8
|
-
brand: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function makeKeyStore(config: KeyStoreConfig) {
|
|
12
|
-
const wrapKey = (soulKey: string) => `${config.brand}_notekey:${soulKey}`;
|
|
13
|
-
return {
|
|
14
|
-
get: (k: string): string | null => {
|
|
15
|
-
try { return localStorage.getItem(k); } catch { return null; }
|
|
16
|
-
},
|
|
17
|
-
set: (k: string, v: string): void => {
|
|
18
|
-
try { localStorage.setItem(k, v); } catch { /* private mode:不阻擋主流程 */ }
|
|
19
|
-
},
|
|
20
|
-
remove: (k: string): void => {
|
|
21
|
-
try { localStorage.removeItem(k); } catch { /* noop */ }
|
|
22
|
-
},
|
|
23
|
-
noteKeyWrap: wrapKey,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type KeyStore = ReturnType<typeof makeKeyStore>;
|