@tacet-ink/journal-core 0.2.5 → 0.3.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.
@@ -0,0 +1,84 @@
1
+ /**
2
+ * pinlock.ts — 本機 PIN 鎖定包裹原語(jr1p.,「開啟時鎖定」,2026-09-24 動工)。
3
+ *
4
+ * 威脅模型:裝置被順手翻看/拔走。raw noteKey 摘除、以 PIN 派生 KEK 包裹,
5
+ * 鎖定期間 localStorage 無 raw key(鐵律 8 誠實姿態的實質升級)。
6
+ * jr1p. 刻意用 PBKDF2-SHA256 600k,不用 Argon2id:解鎖每次啟動都要經歷,
7
+ * 鎖定閘要即時性+省電;鎖定閘非登入 oracle 戰場(離線爆破 6 位數字 ≈ 單 GPU 數天=門檻不是牆)。
8
+ * KDF 升級走帶內版本化(新前綴),禁原地改語意。
9
+ *
10
+ * payload(pinSalt 內嵌自描述,與 jr2w./jr3d. 同構):
11
+ * jr1p. + b64( pinSalt[16] ‖ iv[12] ‖ GCM(KEK, hex(noteKey), aad='notekey-pinlock') ),嚴格 108B
12
+ * KEK = PBKDF2-SHA256(pinNorm, 'tacet-pinlock-v1:' + hex(pinSalt), 600k) → AES-GCM key(nonextractable)
13
+ * ⚠️ unwrap 輸出 noteKey 一律 extractable=true(鐵律 1:要能再包裹/匯出);KEK nonextractable。
14
+ * ⚠️ cfg 未配置 = 全拒(wrapDual/wrapShare3 同款 opt-in 律,兄弟 fork 零影響)。
15
+ * ⚠️ 語意分離:jr1p. 是本機裝置閘,與登入第二因子(jr2w./jr3d.)契約、payload、威脅模型全分離。
16
+ */
17
+ import { b64, unb64, toHex, hexToBytes, decryptWithKey, importAesGcm, normalizePin, } from './note-crypto.js';
18
+ /** 雙因子 pin 段等級(2M)過重:鎖定閘要即時性,600k 與 jr1w. 同級。 */
19
+ export const PINLOCK_ITERATIONS = 600_000;
20
+ const SALT_LEN = 16;
21
+ const IV_LEN = 12;
22
+ /** pinSalt(16) + iv(12) + ct(hex 字串 64B + GCM tag 16B) = 108B 固定。 */
23
+ const PAYLOAD_LEN = SALT_LEN + IV_LEN + 80;
24
+ /** KEK = PBKDF2-SHA256(pinNorm, pinLockSaltPrefix + hex(pinSalt), 600k) → AES-GCM key(nonextractable)。 */
25
+ async function deriveLockKek(pinNorm, pinSalt, cfg) {
26
+ if (!cfg.pinLock || !cfg.pinLockSaltPrefix || !cfg.pinLockAad)
27
+ throw new Error('ERR_PINLOCK_NOT_CONFIGURED');
28
+ const keyMat = await crypto.subtle.importKey('raw', new TextEncoder().encode(pinNorm), 'PBKDF2', false, ['deriveKey']);
29
+ return crypto.subtle.deriveKey({
30
+ name: 'PBKDF2',
31
+ hash: 'SHA-256',
32
+ salt: new TextEncoder().encode(cfg.pinLockSaltPrefix + toHex(pinSalt)),
33
+ iterations: PINLOCK_ITERATIONS,
34
+ }, keyMat, { name: 'AES-GCM', length: 256 }, false, // KEK nonextractable
35
+ ['encrypt', 'decrypt']);
36
+ }
37
+ /** jr1p. 包裹:payload = pinSalt[16] ‖ iv[12] ‖ GCM(KEK, hex(noteKey), aad);全自描述(salt 內嵌)。
38
+ * 鹽內嵌家族(jr2w./jr3d./jr1p.)自帶三段組裝——sealNoteKey 是鹽外置家族形(iv‖ct),
39
+ * 內嵌鹽的 pinSalt 前綴不在其契約內(毒化輪回歸實證:亂收口=unwrap 恆 null)。 */
40
+ export async function wrapNoteKeyPinLock(cfg, noteKey, pin) {
41
+ if (!cfg.pinLock || !cfg.pinLockSaltPrefix || !cfg.pinLockAad)
42
+ throw new Error('ERR_PINLOCK_NOT_CONFIGURED');
43
+ const pinNorm = normalizePin(pin);
44
+ if (!pinNorm)
45
+ throw new Error('ERR_PINLOCK_EMPTY');
46
+ const pinSalt = crypto.getRandomValues(new Uint8Array(SALT_LEN));
47
+ const kek = await deriveLockKek(pinNorm, pinSalt, cfg);
48
+ const rawHex = toHex(new Uint8Array(await crypto.subtle.exportKey('raw', noteKey)));
49
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LEN));
50
+ const ct = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv, additionalData: new TextEncoder().encode(cfg.pinLockAad) }, kek, new TextEncoder().encode(rawHex)));
51
+ const payload = new Uint8Array(PAYLOAD_LEN);
52
+ payload.set(pinSalt, 0);
53
+ payload.set(iv, SALT_LEN);
54
+ payload.set(ct, SALT_LEN + IV_LEN);
55
+ return cfg.pinLock + b64(payload);
56
+ }
57
+ /** jr1p. 解包:全自描述;任何不符(前綴/長度/PIN 空/AAD)回 null 不拋;成功 → noteKey extractable=true(鐵律 1)。
58
+ * 嚴格檢查為鹽內嵌族自有(payload 嚴格 108B+rawHex hex 形檢查;
59
+ * hexToBytes fail-closed 由 try/catch 承接=行為不變)——不經 openNoteKey 本體
60
+ * (openNoteKey 是鹽外置家族 92B 形,pinSalt 前綴不在其契約內)。 */
61
+ export async function unwrapNoteKeyPinLock(cfg, wrapped, pin) {
62
+ try {
63
+ if (!cfg.pinLock || !cfg.pinLockSaltPrefix || !cfg.pinLockAad)
64
+ return null;
65
+ if (!wrapped.startsWith(cfg.pinLock))
66
+ return null;
67
+ const payload = unb64(wrapped.slice(cfg.pinLock.length));
68
+ if (payload.length !== PAYLOAD_LEN)
69
+ return null;
70
+ const pinSalt = payload.slice(0, SALT_LEN);
71
+ const ivPrefixedCt = payload.slice(SALT_LEN); // 鹽內嵌家族:payload = pinSalt[16] ‖ iv[12] ‖ ct
72
+ const pinNorm = normalizePin(pin);
73
+ if (!pinNorm)
74
+ return null;
75
+ const kek = await deriveLockKek(pinNorm, pinSalt, cfg);
76
+ const rawHex = await decryptWithKey(kek, ivPrefixedCt, cfg.pinLockAad);
77
+ if (!rawHex || rawHex.length !== 64 || !/^[0-9a-f]+$/.test(rawHex))
78
+ return null;
79
+ return importAesGcm(hexToBytes(rawHex), true);
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * wordlist.ts — BIP39 英文詞表(2048 詞,標準常數;非任何套件的碼)。
3
+ * 源:BIP39 官方 english wordlist(經 @scure/bip39 v2.4.0 wordlists/english.js 校對)。
4
+ * MIT (c) 2022 Patricio Palladino, Paul Miller(@scure/bip39)。
5
+ * 契約:全小寫 a-z、恰 2048 詞、索引即 11-bit 編碼值;只供 kit 轉寫層使用。
6
+ */
7
+ export declare const wordlist: readonly string[];
@@ -0,0 +1,264 @@
1
+ /**
2
+ * wordlist.ts — BIP39 英文詞表(2048 詞,標準常數;非任何套件的碼)。
3
+ * 源:BIP39 官方 english wordlist(經 @scure/bip39 v2.4.0 wordlists/english.js 校對)。
4
+ * MIT (c) 2022 Patricio Palladino, Paul Miller(@scure/bip39)。
5
+ * 契約:全小寫 a-z、恰 2048 詞、索引即 11-bit 編碼值;只供 kit 轉寫層使用。
6
+ */
7
+ export const wordlist = Object.freeze([
8
+ 'abandon', 'ability', 'able', 'about', 'above', 'absent', 'absorb', 'abstract',
9
+ 'absurd', 'abuse', 'access', 'accident', 'account', 'accuse', 'achieve', 'acid',
10
+ 'acoustic', 'acquire', 'across', 'act', 'action', 'actor', 'actress', 'actual',
11
+ 'adapt', 'add', 'addict', 'address', 'adjust', 'admit', 'adult', 'advance',
12
+ 'advice', 'aerobic', 'affair', 'afford', 'afraid', 'again', 'age', 'agent',
13
+ 'agree', 'ahead', 'aim', 'air', 'airport', 'aisle', 'alarm', 'album',
14
+ 'alcohol', 'alert', 'alien', 'all', 'alley', 'allow', 'almost', 'alone',
15
+ 'alpha', 'already', 'also', 'alter', 'always', 'amateur', 'amazing', 'among',
16
+ 'amount', 'amused', 'analyst', 'anchor', 'ancient', 'anger', 'angle', 'angry',
17
+ 'animal', 'ankle', 'announce', 'annual', 'another', 'answer', 'antenna', 'antique',
18
+ 'anxiety', 'any', 'apart', 'apology', 'appear', 'apple', 'approve', 'april',
19
+ 'arch', 'arctic', 'area', 'arena', 'argue', 'arm', 'armed', 'armor',
20
+ 'army', 'around', 'arrange', 'arrest', 'arrive', 'arrow', 'art', 'artefact',
21
+ 'artist', 'artwork', 'ask', 'aspect', 'assault', 'asset', 'assist', 'assume',
22
+ 'asthma', 'athlete', 'atom', 'attack', 'attend', 'attitude', 'attract', 'auction',
23
+ 'audit', 'august', 'aunt', 'author', 'auto', 'autumn', 'average', 'avocado',
24
+ 'avoid', 'awake', 'aware', 'away', 'awesome', 'awful', 'awkward', 'axis',
25
+ 'baby', 'bachelor', 'bacon', 'badge', 'bag', 'balance', 'balcony', 'ball',
26
+ 'bamboo', 'banana', 'banner', 'bar', 'barely', 'bargain', 'barrel', 'base',
27
+ 'basic', 'basket', 'battle', 'beach', 'bean', 'beauty', 'because', 'become',
28
+ 'beef', 'before', 'begin', 'behave', 'behind', 'believe', 'below', 'belt',
29
+ 'bench', 'benefit', 'best', 'betray', 'better', 'between', 'beyond', 'bicycle',
30
+ 'bid', 'bike', 'bind', 'biology', 'bird', 'birth', 'bitter', 'black',
31
+ 'blade', 'blame', 'blanket', 'blast', 'bleak', 'bless', 'blind', 'blood',
32
+ 'blossom', 'blouse', 'blue', 'blur', 'blush', 'board', 'boat', 'body',
33
+ 'boil', 'bomb', 'bone', 'bonus', 'book', 'boost', 'border', 'boring',
34
+ 'borrow', 'boss', 'bottom', 'bounce', 'box', 'boy', 'bracket', 'brain',
35
+ 'brand', 'brass', 'brave', 'bread', 'breeze', 'brick', 'bridge', 'brief',
36
+ 'bright', 'bring', 'brisk', 'broccoli', 'broken', 'bronze', 'broom', 'brother',
37
+ 'brown', 'brush', 'bubble', 'buddy', 'budget', 'buffalo', 'build', 'bulb',
38
+ 'bulk', 'bullet', 'bundle', 'bunker', 'burden', 'burger', 'burst', 'bus',
39
+ 'business', 'busy', 'butter', 'buyer', 'buzz', 'cabbage', 'cabin', 'cable',
40
+ 'cactus', 'cage', 'cake', 'call', 'calm', 'camera', 'camp', 'can',
41
+ 'canal', 'cancel', 'candy', 'cannon', 'canoe', 'canvas', 'canyon', 'capable',
42
+ 'capital', 'captain', 'car', 'carbon', 'card', 'cargo', 'carpet', 'carry',
43
+ 'cart', 'case', 'cash', 'casino', 'castle', 'casual', 'cat', 'catalog',
44
+ 'catch', 'category', 'cattle', 'caught', 'cause', 'caution', 'cave', 'ceiling',
45
+ 'celery', 'cement', 'census', 'century', 'cereal', 'certain', 'chair', 'chalk',
46
+ 'champion', 'change', 'chaos', 'chapter', 'charge', 'chase', 'chat', 'cheap',
47
+ 'check', 'cheese', 'chef', 'cherry', 'chest', 'chicken', 'chief', 'child',
48
+ 'chimney', 'choice', 'choose', 'chronic', 'chuckle', 'chunk', 'churn', 'cigar',
49
+ 'cinnamon', 'circle', 'citizen', 'city', 'civil', 'claim', 'clap', 'clarify',
50
+ 'claw', 'clay', 'clean', 'clerk', 'clever', 'click', 'client', 'cliff',
51
+ 'climb', 'clinic', 'clip', 'clock', 'clog', 'close', 'cloth', 'cloud',
52
+ 'clown', 'club', 'clump', 'cluster', 'clutch', 'coach', 'coast', 'coconut',
53
+ 'code', 'coffee', 'coil', 'coin', 'collect', 'color', 'column', 'combine',
54
+ 'come', 'comfort', 'comic', 'common', 'company', 'concert', 'conduct', 'confirm',
55
+ 'congress', 'connect', 'consider', 'control', 'convince', 'cook', 'cool', 'copper',
56
+ 'copy', 'coral', 'core', 'corn', 'correct', 'cost', 'cotton', 'couch',
57
+ 'country', 'couple', 'course', 'cousin', 'cover', 'coyote', 'crack', 'cradle',
58
+ 'craft', 'cram', 'crane', 'crash', 'crater', 'crawl', 'crazy', 'cream',
59
+ 'credit', 'creek', 'crew', 'cricket', 'crime', 'crisp', 'critic', 'crop',
60
+ 'cross', 'crouch', 'crowd', 'crucial', 'cruel', 'cruise', 'crumble', 'crunch',
61
+ 'crush', 'cry', 'crystal', 'cube', 'culture', 'cup', 'cupboard', 'curious',
62
+ 'current', 'curtain', 'curve', 'cushion', 'custom', 'cute', 'cycle', 'dad',
63
+ 'damage', 'damp', 'dance', 'danger', 'daring', 'dash', 'daughter', 'dawn',
64
+ 'day', 'deal', 'debate', 'debris', 'decade', 'december', 'decide', 'decline',
65
+ 'decorate', 'decrease', 'deer', 'defense', 'define', 'defy', 'degree', 'delay',
66
+ 'deliver', 'demand', 'demise', 'denial', 'dentist', 'deny', 'depart', 'depend',
67
+ 'deposit', 'depth', 'deputy', 'derive', 'describe', 'desert', 'design', 'desk',
68
+ 'despair', 'destroy', 'detail', 'detect', 'develop', 'device', 'devote', 'diagram',
69
+ 'dial', 'diamond', 'diary', 'dice', 'diesel', 'diet', 'differ', 'digital',
70
+ 'dignity', 'dilemma', 'dinner', 'dinosaur', 'direct', 'dirt', 'disagree', 'discover',
71
+ 'disease', 'dish', 'dismiss', 'disorder', 'display', 'distance', 'divert', 'divide',
72
+ 'divorce', 'dizzy', 'doctor', 'document', 'dog', 'doll', 'dolphin', 'domain',
73
+ 'donate', 'donkey', 'donor', 'door', 'dose', 'double', 'dove', 'draft',
74
+ 'dragon', 'drama', 'drastic', 'draw', 'dream', 'dress', 'drift', 'drill',
75
+ 'drink', 'drip', 'drive', 'drop', 'drum', 'dry', 'duck', 'dumb',
76
+ 'dune', 'during', 'dust', 'dutch', 'duty', 'dwarf', 'dynamic', 'eager',
77
+ 'eagle', 'early', 'earn', 'earth', 'easily', 'east', 'easy', 'echo',
78
+ 'ecology', 'economy', 'edge', 'edit', 'educate', 'effort', 'egg', 'eight',
79
+ 'either', 'elbow', 'elder', 'electric', 'elegant', 'element', 'elephant', 'elevator',
80
+ 'elite', 'else', 'embark', 'embody', 'embrace', 'emerge', 'emotion', 'employ',
81
+ 'empower', 'empty', 'enable', 'enact', 'end', 'endless', 'endorse', 'enemy',
82
+ 'energy', 'enforce', 'engage', 'engine', 'enhance', 'enjoy', 'enlist', 'enough',
83
+ 'enrich', 'enroll', 'ensure', 'enter', 'entire', 'entry', 'envelope', 'episode',
84
+ 'equal', 'equip', 'era', 'erase', 'erode', 'erosion', 'error', 'erupt',
85
+ 'escape', 'essay', 'essence', 'estate', 'eternal', 'ethics', 'evidence', 'evil',
86
+ 'evoke', 'evolve', 'exact', 'example', 'excess', 'exchange', 'excite', 'exclude',
87
+ 'excuse', 'execute', 'exercise', 'exhaust', 'exhibit', 'exile', 'exist', 'exit',
88
+ 'exotic', 'expand', 'expect', 'expire', 'explain', 'expose', 'express', 'extend',
89
+ 'extra', 'eye', 'eyebrow', 'fabric', 'face', 'faculty', 'fade', 'faint',
90
+ 'faith', 'fall', 'false', 'fame', 'family', 'famous', 'fan', 'fancy',
91
+ 'fantasy', 'farm', 'fashion', 'fat', 'fatal', 'father', 'fatigue', 'fault',
92
+ 'favorite', 'feature', 'february', 'federal', 'fee', 'feed', 'feel', 'female',
93
+ 'fence', 'festival', 'fetch', 'fever', 'few', 'fiber', 'fiction', 'field',
94
+ 'figure', 'file', 'film', 'filter', 'final', 'find', 'fine', 'finger',
95
+ 'finish', 'fire', 'firm', 'first', 'fiscal', 'fish', 'fit', 'fitness',
96
+ 'fix', 'flag', 'flame', 'flash', 'flat', 'flavor', 'flee', 'flight',
97
+ 'flip', 'float', 'flock', 'floor', 'flower', 'fluid', 'flush', 'fly',
98
+ 'foam', 'focus', 'fog', 'foil', 'fold', 'follow', 'food', 'foot',
99
+ 'force', 'forest', 'forget', 'fork', 'fortune', 'forum', 'forward', 'fossil',
100
+ 'foster', 'found', 'fox', 'fragile', 'frame', 'frequent', 'fresh', 'friend',
101
+ 'fringe', 'frog', 'front', 'frost', 'frown', 'frozen', 'fruit', 'fuel',
102
+ 'fun', 'funny', 'furnace', 'fury', 'future', 'gadget', 'gain', 'galaxy',
103
+ 'gallery', 'game', 'gap', 'garage', 'garbage', 'garden', 'garlic', 'garment',
104
+ 'gas', 'gasp', 'gate', 'gather', 'gauge', 'gaze', 'general', 'genius',
105
+ 'genre', 'gentle', 'genuine', 'gesture', 'ghost', 'giant', 'gift', 'giggle',
106
+ 'ginger', 'giraffe', 'girl', 'give', 'glad', 'glance', 'glare', 'glass',
107
+ 'glide', 'glimpse', 'globe', 'gloom', 'glory', 'glove', 'glow', 'glue',
108
+ 'goat', 'goddess', 'gold', 'good', 'goose', 'gorilla', 'gospel', 'gossip',
109
+ 'govern', 'gown', 'grab', 'grace', 'grain', 'grant', 'grape', 'grass',
110
+ 'gravity', 'great', 'green', 'grid', 'grief', 'grit', 'grocery', 'group',
111
+ 'grow', 'grunt', 'guard', 'guess', 'guide', 'guilt', 'guitar', 'gun',
112
+ 'gym', 'habit', 'hair', 'half', 'hammer', 'hamster', 'hand', 'happy',
113
+ 'harbor', 'hard', 'harsh', 'harvest', 'hat', 'have', 'hawk', 'hazard',
114
+ 'head', 'health', 'heart', 'heavy', 'hedgehog', 'height', 'hello', 'helmet',
115
+ 'help', 'hen', 'hero', 'hidden', 'high', 'hill', 'hint', 'hip',
116
+ 'hire', 'history', 'hobby', 'hockey', 'hold', 'hole', 'holiday', 'hollow',
117
+ 'home', 'honey', 'hood', 'hope', 'horn', 'horror', 'horse', 'hospital',
118
+ 'host', 'hotel', 'hour', 'hover', 'hub', 'huge', 'human', 'humble',
119
+ 'humor', 'hundred', 'hungry', 'hunt', 'hurdle', 'hurry', 'hurt', 'husband',
120
+ 'hybrid', 'ice', 'icon', 'idea', 'identify', 'idle', 'ignore', 'ill',
121
+ 'illegal', 'illness', 'image', 'imitate', 'immense', 'immune', 'impact', 'impose',
122
+ 'improve', 'impulse', 'inch', 'include', 'income', 'increase', 'index', 'indicate',
123
+ 'indoor', 'industry', 'infant', 'inflict', 'inform', 'inhale', 'inherit', 'initial',
124
+ 'inject', 'injury', 'inmate', 'inner', 'innocent', 'input', 'inquiry', 'insane',
125
+ 'insect', 'inside', 'inspire', 'install', 'intact', 'interest', 'into', 'invest',
126
+ 'invite', 'involve', 'iron', 'island', 'isolate', 'issue', 'item', 'ivory',
127
+ 'jacket', 'jaguar', 'jar', 'jazz', 'jealous', 'jeans', 'jelly', 'jewel',
128
+ 'job', 'join', 'joke', 'journey', 'joy', 'judge', 'juice', 'jump',
129
+ 'jungle', 'junior', 'junk', 'just', 'kangaroo', 'keen', 'keep', 'ketchup',
130
+ 'key', 'kick', 'kid', 'kidney', 'kind', 'kingdom', 'kiss', 'kit',
131
+ 'kitchen', 'kite', 'kitten', 'kiwi', 'knee', 'knife', 'knock', 'know',
132
+ 'lab', 'label', 'labor', 'ladder', 'lady', 'lake', 'lamp', 'language',
133
+ 'laptop', 'large', 'later', 'latin', 'laugh', 'laundry', 'lava', 'law',
134
+ 'lawn', 'lawsuit', 'layer', 'lazy', 'leader', 'leaf', 'learn', 'leave',
135
+ 'lecture', 'left', 'leg', 'legal', 'legend', 'leisure', 'lemon', 'lend',
136
+ 'length', 'lens', 'leopard', 'lesson', 'letter', 'level', 'liar', 'liberty',
137
+ 'library', 'license', 'life', 'lift', 'light', 'like', 'limb', 'limit',
138
+ 'link', 'lion', 'liquid', 'list', 'little', 'live', 'lizard', 'load',
139
+ 'loan', 'lobster', 'local', 'lock', 'logic', 'lonely', 'long', 'loop',
140
+ 'lottery', 'loud', 'lounge', 'love', 'loyal', 'lucky', 'luggage', 'lumber',
141
+ 'lunar', 'lunch', 'luxury', 'lyrics', 'machine', 'mad', 'magic', 'magnet',
142
+ 'maid', 'mail', 'main', 'major', 'make', 'mammal', 'man', 'manage',
143
+ 'mandate', 'mango', 'mansion', 'manual', 'maple', 'marble', 'march', 'margin',
144
+ 'marine', 'market', 'marriage', 'mask', 'mass', 'master', 'match', 'material',
145
+ 'math', 'matrix', 'matter', 'maximum', 'maze', 'meadow', 'mean', 'measure',
146
+ 'meat', 'mechanic', 'medal', 'media', 'melody', 'melt', 'member', 'memory',
147
+ 'mention', 'menu', 'mercy', 'merge', 'merit', 'merry', 'mesh', 'message',
148
+ 'metal', 'method', 'middle', 'midnight', 'milk', 'million', 'mimic', 'mind',
149
+ 'minimum', 'minor', 'minute', 'miracle', 'mirror', 'misery', 'miss', 'mistake',
150
+ 'mix', 'mixed', 'mixture', 'mobile', 'model', 'modify', 'mom', 'moment',
151
+ 'monitor', 'monkey', 'monster', 'month', 'moon', 'moral', 'more', 'morning',
152
+ 'mosquito', 'mother', 'motion', 'motor', 'mountain', 'mouse', 'move', 'movie',
153
+ 'much', 'muffin', 'mule', 'multiply', 'muscle', 'museum', 'mushroom', 'music',
154
+ 'must', 'mutual', 'myself', 'mystery', 'myth', 'naive', 'name', 'napkin',
155
+ 'narrow', 'nasty', 'nation', 'nature', 'near', 'neck', 'need', 'negative',
156
+ 'neglect', 'neither', 'nephew', 'nerve', 'nest', 'net', 'network', 'neutral',
157
+ 'never', 'news', 'next', 'nice', 'night', 'noble', 'noise', 'nominee',
158
+ 'noodle', 'normal', 'north', 'nose', 'notable', 'note', 'nothing', 'notice',
159
+ 'novel', 'now', 'nuclear', 'number', 'nurse', 'nut', 'oak', 'obey',
160
+ 'object', 'oblige', 'obscure', 'observe', 'obtain', 'obvious', 'occur', 'ocean',
161
+ 'october', 'odor', 'off', 'offer', 'office', 'often', 'oil', 'okay',
162
+ 'old', 'olive', 'olympic', 'omit', 'once', 'one', 'onion', 'online',
163
+ 'only', 'open', 'opera', 'opinion', 'oppose', 'option', 'orange', 'orbit',
164
+ 'orchard', 'order', 'ordinary', 'organ', 'orient', 'original', 'orphan', 'ostrich',
165
+ 'other', 'outdoor', 'outer', 'output', 'outside', 'oval', 'oven', 'over',
166
+ 'own', 'owner', 'oxygen', 'oyster', 'ozone', 'pact', 'paddle', 'page',
167
+ 'pair', 'palace', 'palm', 'panda', 'panel', 'panic', 'panther', 'paper',
168
+ 'parade', 'parent', 'park', 'parrot', 'party', 'pass', 'patch', 'path',
169
+ 'patient', 'patrol', 'pattern', 'pause', 'pave', 'payment', 'peace', 'peanut',
170
+ 'pear', 'peasant', 'pelican', 'pen', 'penalty', 'pencil', 'people', 'pepper',
171
+ 'perfect', 'permit', 'person', 'pet', 'phone', 'photo', 'phrase', 'physical',
172
+ 'piano', 'picnic', 'picture', 'piece', 'pig', 'pigeon', 'pill', 'pilot',
173
+ 'pink', 'pioneer', 'pipe', 'pistol', 'pitch', 'pizza', 'place', 'planet',
174
+ 'plastic', 'plate', 'play', 'please', 'pledge', 'pluck', 'plug', 'plunge',
175
+ 'poem', 'poet', 'point', 'polar', 'pole', 'police', 'pond', 'pony',
176
+ 'pool', 'popular', 'portion', 'position', 'possible', 'post', 'potato', 'pottery',
177
+ 'poverty', 'powder', 'power', 'practice', 'praise', 'predict', 'prefer', 'prepare',
178
+ 'present', 'pretty', 'prevent', 'price', 'pride', 'primary', 'print', 'priority',
179
+ 'prison', 'private', 'prize', 'problem', 'process', 'produce', 'profit', 'program',
180
+ 'project', 'promote', 'proof', 'property', 'prosper', 'protect', 'proud', 'provide',
181
+ 'public', 'pudding', 'pull', 'pulp', 'pulse', 'pumpkin', 'punch', 'pupil',
182
+ 'puppy', 'purchase', 'purity', 'purpose', 'purse', 'push', 'put', 'puzzle',
183
+ 'pyramid', 'quality', 'quantum', 'quarter', 'question', 'quick', 'quit', 'quiz',
184
+ 'quote', 'rabbit', 'raccoon', 'race', 'rack', 'radar', 'radio', 'rail',
185
+ 'rain', 'raise', 'rally', 'ramp', 'ranch', 'random', 'range', 'rapid',
186
+ 'rare', 'rate', 'rather', 'raven', 'raw', 'razor', 'ready', 'real',
187
+ 'reason', 'rebel', 'rebuild', 'recall', 'receive', 'recipe', 'record', 'recycle',
188
+ 'reduce', 'reflect', 'reform', 'refuse', 'region', 'regret', 'regular', 'reject',
189
+ 'relax', 'release', 'relief', 'rely', 'remain', 'remember', 'remind', 'remove',
190
+ 'render', 'renew', 'rent', 'reopen', 'repair', 'repeat', 'replace', 'report',
191
+ 'require', 'rescue', 'resemble', 'resist', 'resource', 'response', 'result', 'retire',
192
+ 'retreat', 'return', 'reunion', 'reveal', 'review', 'reward', 'rhythm', 'rib',
193
+ 'ribbon', 'rice', 'rich', 'ride', 'ridge', 'rifle', 'right', 'rigid',
194
+ 'ring', 'riot', 'ripple', 'risk', 'ritual', 'rival', 'river', 'road',
195
+ 'roast', 'robot', 'robust', 'rocket', 'romance', 'roof', 'rookie', 'room',
196
+ 'rose', 'rotate', 'rough', 'round', 'route', 'royal', 'rubber', 'rude',
197
+ 'rug', 'rule', 'run', 'runway', 'rural', 'sad', 'saddle', 'sadness',
198
+ 'safe', 'sail', 'salad', 'salmon', 'salon', 'salt', 'salute', 'same',
199
+ 'sample', 'sand', 'satisfy', 'satoshi', 'sauce', 'sausage', 'save', 'say',
200
+ 'scale', 'scan', 'scare', 'scatter', 'scene', 'scheme', 'school', 'science',
201
+ 'scissors', 'scorpion', 'scout', 'scrap', 'screen', 'script', 'scrub', 'sea',
202
+ 'search', 'season', 'seat', 'second', 'secret', 'section', 'security', 'seed',
203
+ 'seek', 'segment', 'select', 'sell', 'seminar', 'senior', 'sense', 'sentence',
204
+ 'series', 'service', 'session', 'settle', 'setup', 'seven', 'shadow', 'shaft',
205
+ 'shallow', 'share', 'shed', 'shell', 'sheriff', 'shield', 'shift', 'shine',
206
+ 'ship', 'shiver', 'shock', 'shoe', 'shoot', 'shop', 'short', 'shoulder',
207
+ 'shove', 'shrimp', 'shrug', 'shuffle', 'shy', 'sibling', 'sick', 'side',
208
+ 'siege', 'sight', 'sign', 'silent', 'silk', 'silly', 'silver', 'similar',
209
+ 'simple', 'since', 'sing', 'siren', 'sister', 'situate', 'six', 'size',
210
+ 'skate', 'sketch', 'ski', 'skill', 'skin', 'skirt', 'skull', 'slab',
211
+ 'slam', 'sleep', 'slender', 'slice', 'slide', 'slight', 'slim', 'slogan',
212
+ 'slot', 'slow', 'slush', 'small', 'smart', 'smile', 'smoke', 'smooth',
213
+ 'snack', 'snake', 'snap', 'sniff', 'snow', 'soap', 'soccer', 'social',
214
+ 'sock', 'soda', 'soft', 'solar', 'soldier', 'solid', 'solution', 'solve',
215
+ 'someone', 'song', 'soon', 'sorry', 'sort', 'soul', 'sound', 'soup',
216
+ 'source', 'south', 'space', 'spare', 'spatial', 'spawn', 'speak', 'special',
217
+ 'speed', 'spell', 'spend', 'sphere', 'spice', 'spider', 'spike', 'spin',
218
+ 'spirit', 'split', 'spoil', 'sponsor', 'spoon', 'sport', 'spot', 'spray',
219
+ 'spread', 'spring', 'spy', 'square', 'squeeze', 'squirrel', 'stable', 'stadium',
220
+ 'staff', 'stage', 'stairs', 'stamp', 'stand', 'start', 'state', 'stay',
221
+ 'steak', 'steel', 'stem', 'step', 'stereo', 'stick', 'still', 'sting',
222
+ 'stock', 'stomach', 'stone', 'stool', 'story', 'stove', 'strategy', 'street',
223
+ 'strike', 'strong', 'struggle', 'student', 'stuff', 'stumble', 'style', 'subject',
224
+ 'submit', 'subway', 'success', 'such', 'sudden', 'suffer', 'sugar', 'suggest',
225
+ 'suit', 'summer', 'sun', 'sunny', 'sunset', 'super', 'supply', 'supreme',
226
+ 'sure', 'surface', 'surge', 'surprise', 'surround', 'survey', 'suspect', 'sustain',
227
+ 'swallow', 'swamp', 'swap', 'swarm', 'swear', 'sweet', 'swift', 'swim',
228
+ 'swing', 'switch', 'sword', 'symbol', 'symptom', 'syrup', 'system', 'table',
229
+ 'tackle', 'tag', 'tail', 'talent', 'talk', 'tank', 'tape', 'target',
230
+ 'task', 'taste', 'tattoo', 'taxi', 'teach', 'team', 'tell', 'ten',
231
+ 'tenant', 'tennis', 'tent', 'term', 'test', 'text', 'thank', 'that',
232
+ 'theme', 'then', 'theory', 'there', 'they', 'thing', 'this', 'thought',
233
+ 'three', 'thrive', 'throw', 'thumb', 'thunder', 'ticket', 'tide', 'tiger',
234
+ 'tilt', 'timber', 'time', 'tiny', 'tip', 'tired', 'tissue', 'title',
235
+ 'toast', 'tobacco', 'today', 'toddler', 'toe', 'together', 'toilet', 'token',
236
+ 'tomato', 'tomorrow', 'tone', 'tongue', 'tonight', 'tool', 'tooth', 'top',
237
+ 'topic', 'topple', 'torch', 'tornado', 'tortoise', 'toss', 'total', 'tourist',
238
+ 'toward', 'tower', 'town', 'toy', 'track', 'trade', 'traffic', 'tragic',
239
+ 'train', 'transfer', 'trap', 'trash', 'travel', 'tray', 'treat', 'tree',
240
+ 'trend', 'trial', 'tribe', 'trick', 'trigger', 'trim', 'trip', 'trophy',
241
+ 'trouble', 'truck', 'true', 'truly', 'trumpet', 'trust', 'truth', 'try',
242
+ 'tube', 'tuition', 'tumble', 'tuna', 'tunnel', 'turkey', 'turn', 'turtle',
243
+ 'twelve', 'twenty', 'twice', 'twin', 'twist', 'two', 'type', 'typical',
244
+ 'ugly', 'umbrella', 'unable', 'unaware', 'uncle', 'uncover', 'under', 'undo',
245
+ 'unfair', 'unfold', 'unhappy', 'uniform', 'unique', 'unit', 'universe', 'unknown',
246
+ 'unlock', 'until', 'unusual', 'unveil', 'update', 'upgrade', 'uphold', 'upon',
247
+ 'upper', 'upset', 'urban', 'urge', 'usage', 'use', 'used', 'useful',
248
+ 'useless', 'usual', 'utility', 'vacant', 'vacuum', 'vague', 'valid', 'valley',
249
+ 'valve', 'van', 'vanish', 'vapor', 'various', 'vast', 'vault', 'vehicle',
250
+ 'velvet', 'vendor', 'venture', 'venue', 'verb', 'verify', 'version', 'very',
251
+ 'vessel', 'veteran', 'viable', 'vibrant', 'vicious', 'victory', 'video', 'view',
252
+ 'village', 'vintage', 'violin', 'virtual', 'virus', 'visa', 'visit', 'visual',
253
+ 'vital', 'vivid', 'vocal', 'voice', 'void', 'volcano', 'volume', 'vote',
254
+ 'voyage', 'wage', 'wagon', 'wait', 'walk', 'wall', 'walnut', 'want',
255
+ 'warfare', 'warm', 'warrior', 'wash', 'wasp', 'waste', 'water', 'wave',
256
+ 'way', 'wealth', 'weapon', 'wear', 'weasel', 'weather', 'web', 'wedding',
257
+ 'weekend', 'weird', 'welcome', 'west', 'wet', 'whale', 'what', 'wheat',
258
+ 'wheel', 'when', 'where', 'whip', 'whisper', 'wide', 'width', 'wife',
259
+ 'wild', 'will', 'win', 'window', 'wine', 'wing', 'wink', 'winner',
260
+ 'winter', 'wire', 'wisdom', 'wise', 'wish', 'witness', 'wolf', 'woman',
261
+ 'wonder', 'wood', 'wool', 'word', 'work', 'world', 'worry', 'worth',
262
+ 'wrap', 'wreck', 'wrestle', 'wrist', 'write', 'wrong', 'yard', 'year',
263
+ 'yellow', 'you', 'young', 'youth', 'zebra', 'zero', 'zone', 'zoo',
264
+ ]);
@@ -0,0 +1,18 @@
1
+ /**
2
+ * index.ts — 公開 API barrel。
3
+ *
4
+ * 客戶端原語(WebCrypto;Argon2id 瀏覽器載體見 argon2.ts 檔頭 setArgonLoader):
5
+ */
6
+ export { makeHeldKey, generateNoteKey, encryptNote, decryptNote, encryptAttach, decryptAttach, encryptLocal, decryptLocal, wrapNoteKey, unwrapNoteKey, wrapNoteKeyWithRecToken, unwrapNoteKeyWithRecToken, wrapNoteKeyDual, unwrapNoteKeyDual, storeLocalWrap, clearLocalWrap, buildBindPayload, ph1Of, normalizePin, normalizePassphrase, recTokenHash, generateRecToken, PBKDF2_ITERATIONS, PIN_PBKDF2_ITERATIONS, type NoteCryptoConfig, type IdentityProvider, type HeldKey, } from './client/note-crypto.ts';
7
+ export { setArgonLoader, verifyArgonKat, wrapNoteKey3, unwrapNoteKey3, wrapNoteKeyDual3, unwrapNoteKeyDual3, wrapNoteKey4, unwrapNoteKey4, wrapNoteKeyDual4, unwrapNoteKeyDual4, wrapNoteKeyShare3, unwrapNoteKeyShare3, derivePh1Argon, derivePh1ArgonV3, PH1_V2_SALT, PH1_V3_SALT, ARGON_MEMORY_KIB, ARGON_ITERATIONS, ARGON_PARALLELISM, ARGON_TAG_LEN, type Argon3Config, } from './client/argon2.ts';
8
+ export { wrapNoteKeyPinLock, unwrapNoteKeyPinLock, PINLOCK_ITERATIONS, type PinLockConfig, } from './client/pinlock.ts';
9
+ export { generateBip39Words, wordsToRecToken, recTokenToWords, spotCheckIndexes, } from './client/bip39.ts';
10
+ export { makeKeyStore, type KeyStore, type KeyStoreConfig } from './client/keys.ts';
11
+ export { wordlist } from './client/wordlist.ts';
12
+ /** 伺服器端原語(Cloudflare Workers + D1)。 */
13
+ export { makeInboundCipher, isCipherFor, validWrappedKey, validHash64, validSalt, pickKeyPackage, loginRouteCore, errResponse, AUTH_RATE, type AuthRow, type AuthStore, type CipherFormats, type KeyPackage, } from './server/auth.ts';
14
+ export { checkRate, type RateWindow } from './server/ratelimit.ts';
15
+ export { corsResponse, CORS_HEADERS } from './server/cors.ts';
16
+ export { sha256Hex, hashString, generateSessionToken, timingSafeEq, } from './server/hash.ts';
17
+ export type { Env } from './server/env.ts';
18
+ export { PH2_LADDER_TABLE, PH2_LADDER_KIND_LEGACY, PH2_LADDER_KIND_V2, type Ph2LadderKind, type Ph2LadderRow, } from './server/ladder.ts';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * index.ts — 公開 API barrel。
3
+ *
4
+ * 客戶端原語(WebCrypto;Argon2id 瀏覽器載體見 argon2.ts 檔頭 setArgonLoader):
5
+ */
6
+ export { makeHeldKey, generateNoteKey, encryptNote, decryptNote, encryptAttach, decryptAttach, encryptLocal, decryptLocal, wrapNoteKey, unwrapNoteKey, wrapNoteKeyWithRecToken, unwrapNoteKeyWithRecToken, wrapNoteKeyDual, unwrapNoteKeyDual, storeLocalWrap, clearLocalWrap, buildBindPayload, ph1Of, normalizePin, normalizePassphrase, recTokenHash, generateRecToken, PBKDF2_ITERATIONS, PIN_PBKDF2_ITERATIONS, } from './client/note-crypto.js';
7
+ export { setArgonLoader, verifyArgonKat, wrapNoteKey3, unwrapNoteKey3, wrapNoteKeyDual3, unwrapNoteKeyDual3, wrapNoteKey4, unwrapNoteKey4, wrapNoteKeyDual4, unwrapNoteKeyDual4, wrapNoteKeyShare3, unwrapNoteKeyShare3, derivePh1Argon, derivePh1ArgonV3, PH1_V2_SALT, PH1_V3_SALT, ARGON_MEMORY_KIB, ARGON_ITERATIONS, ARGON_PARALLELISM, ARGON_TAG_LEN, } from './client/argon2.js';
8
+ export { wrapNoteKeyPinLock, unwrapNoteKeyPinLock, PINLOCK_ITERATIONS, } from './client/pinlock.js';
9
+ export { generateBip39Words, wordsToRecToken, recTokenToWords, spotCheckIndexes, } from './client/bip39.js';
10
+ export { makeKeyStore } from './client/keys.js';
11
+ export { wordlist } from './client/wordlist.js';
12
+ /** 伺服器端原語(Cloudflare Workers + D1)。 */
13
+ export { makeInboundCipher, isCipherFor, validWrappedKey, validHash64, validSalt, pickKeyPackage, loginRouteCore, errResponse, AUTH_RATE, } from './server/auth.js';
14
+ export { checkRate } from './server/ratelimit.js';
15
+ export { corsResponse, CORS_HEADERS } from './server/cors.js';
16
+ export { sha256Hex, hashString, generateSessionToken, timingSafeEq, } from './server/hash.js';
17
+ export { PH2_LADDER_TABLE, PH2_LADDER_KIND_LEGACY, PH2_LADDER_KIND_V2, } from './server/ladder.js';
@@ -0,0 +1,99 @@
1
+ /**
2
+ * auth.ts — 零知識伺服器端 auth 核心邏輯(品牌/schema 參數化)。
3
+ *
4
+ * 本檔實作的契約:
5
+ * - pass 明文永不過線:線上憑證送 PH1(雜湊形),伺服器存 PH2 = SHA-256(PH1)。
6
+ * PH1 派生方式由各產品自定義(快雜湊或 Argon2id 派生皆可)——本層只驗
7
+ * hex64 形,不驗 PH1 派生方式(伺服器零知識,無從也無需區分形別)。
8
+ * - 入庫前密文/包裹格式驗證(inboundCipher/isCipherFor/validWrappedKey/
9
+ * pickKeyPackage 等,前綴由 config 注入)。
10
+ * - /auth/login 核心動線(loginRouteCore):rate-limit 閘 → PH1 驗形 →
11
+ * PH2 → store 查詢(現值 miss → ladder 查表守衛——0.2.4 內建,optional 未配置
12
+ * 即退場)/建號 → session token 簽發(30 天)。
13
+ *
14
+ * 呼叫端(fork)職責——本檔只定義介面,機制在呼叫端實作:
15
+ * - identity 生成:AuthStore.createUser 由各 fork 自決(隨機 account_id 或
16
+ * 客戶端帶入語意皆可);PH2 UNIQUE 衝突(兩個同時首登同密語)是 store
17
+ * 責任——tacet 參考實作:UNIQUE 失敗重查既有列回 userKey(非 5xx)。
18
+ * - hash-ladder 雙軌(舊式 PH1 直比 → 升級寫回 PH2)與密語重設後的 session
19
+ * 撤銷時機:fork 呼叫端接線(revokeAllSessions 介面已備);ladder 查表守衛已
20
+ * 內建 loginRouteCore(optional——fork 只需配 ladderLookup+0011 migration,接線零寫)。
21
+ * - 常數時間比較 timingSafeEq:./hash.ts 匯出(login 動線經 PH2 查詢不直比)。
22
+ *
23
+ * 不變量(勿破壞):
24
+ * - 金鑰包裹欄組(wrapped+salt)缺一整組放棄——pair 檢查本體在 pickKeyPackage。
25
+ * - recPkg 與 recHash 成對出現=呼叫端(fork store)職責:本層介面兩參各自可空、
26
+ * 不做 pair 檢查(裁定照此收口;pickKeyPackage 是同職責的成對檢查先例)。
27
+ */
28
+ import { type RateWindow } from './ratelimit.ts';
29
+ import type { Env } from './env.ts';
30
+ export interface CipherFormats {
31
+ /** 密文前綴家族字面(startsWith 語意,非 regex 來源),如 'jr1b.'。 */
32
+ cipherPrefixes: [string, string];
33
+ /** 金鑰包裹前綴,如 'jr1w.'。 */
34
+ wrapPrefix: string;
35
+ /** 密文上限:UTF-16 code unit 計數(.length 語意;非 UTF-8 bytes)。 */
36
+ cipherMax: number;
37
+ }
38
+ /**
39
+ * 入庫前校正:前綴密文原樣入庫;超限密文/編碼丟棄(截斷必壞)、明文截到上限照收。
40
+ * cipherMax(UTF-16 單位)同為明文截斷帽:明文相容層無 opt-out——明文一律照收、
41
+ * 截到 cipherMax 入庫(JSDoc 幽靈參數 plainMax 已摘除)。
42
+ */
43
+ export declare function makeInboundCipher(c: CipherFormats): (raw: unknown) => string | null;
44
+ export declare function isCipherFor(text: string | null | undefined, c: CipherFormats): boolean;
45
+ export declare function validWrappedKey(v: unknown, wrapPrefix: string | string[]): string | null;
46
+ export declare function validHash64(v: unknown): string | null;
47
+ export declare function validSalt(v: unknown): string | null;
48
+ /** 綁定/復原共用的金鑰包裹欄組:wrapped 與 salt 必須成對,缺一整組放棄。 */
49
+ export declare function pickKeyPackage(body: any, wrapPrefix: string | string[]): {
50
+ wrapped: string | null;
51
+ salt: string | null;
52
+ };
53
+ export interface KeyPackage {
54
+ wrapped: string;
55
+ salt: string;
56
+ }
57
+ export interface AuthStore {
58
+ /** 以 PH2 查身份(PH2 UNIQUE;tacet:隨機 account_id 在此建立)。 */
59
+ findByIdentityQuery(ph2: string): Promise<AuthRow | null>;
60
+ /** 建立(identity 生成由各 fork 自決——見檔頭「呼叫端職責」;PH2 UNIQUE 衝突亦是 store 責任)。 */
61
+ createUser(ph2: string): Promise<string>;
62
+ getByUserKey(key: string): Promise<AuthRow | null>;
63
+ getByRecHash(recHash: string): Promise<AuthRow | null>;
64
+ setWrapped(userKey: string, ph2: string, pkg: KeyPackage, recPkg: string | null, recHash: string | null): Promise<void>;
65
+ /** v3 重鑰:舊值入 ladder 表+ph2/wrapped/salt/wrappedRec/rec_hash
66
+ * 五欄同列覆蓋(rec 欄在場才覆蓋——四欄形契約面不動;fork 實作三語句同批寫入)。 */
67
+ rekeyWithLadder?(userKey: string, oldPh2: string, oldKind: 'legacy' | 'v2', ph2: string, pkg: KeyPackage, recPkg: string | null, recHash: string | null): Promise<void>;
68
+ /**
69
+ * ladder 查表:以「舊 ph2 值」(v2/legacy 形)查 ladder 表,命中回帳戶 id。
70
+ * loginRouteCore 內建接線(0.2.4 外審 #8 幽靈守衛上位):現值 miss → ladderLookup →
71
+ * 命中=回舊帳(不建幽靈;ph2Kind 語意照 ladder.ts 檔頭註解——core 端回帳序:
72
+ * ladder hit → getByUserKey → 可續 buildSession),兩面皆 miss 才 createUser。
73
+ * 未配置(optional undefined)=查表面退場(現行單查行為零變——向後相容:
74
+ * fork 未配 ladder(0011 未上)時行為與 0.2.3 恆等,sennight/vestige 未來接 core 也零炸)。
75
+ * 錯誤處理照 tacet store.ts 母型:lookup 拋任何錯(0011 未上的 ladder 表缺席、ladder store
76
+ * 停機故障)=fail-open 視同 miss,不炸登入(ladder store 停機中登入=建幽靈——fail-open
77
+ * 語意的已知代價;接線端 ladderGuard 統一 catch 只包 lookup)+非 lookup 錯誤照常外拋。
78
+ */
79
+ ladderLookup?(oldPh2: string): Promise<string | null>;
80
+ /** ladder 入表(遷移線用;upsert冪等)。 */
81
+ ladderInsert?(accountId: string, oldPh2: string, oldKind: 'legacy' | 'v2'): Promise<void>;
82
+ /** ladder 刪列(直銷面;遷移批次同批或後續呼叫端承擔)。 */
83
+ ladderDelete?(accountId: string): Promise<void>;
84
+ revokeAllSessions(userKey: string): Promise<void>;
85
+ insertSession(tokenHash: string, userKey: string, expiresAt: number): Promise<void>;
86
+ }
87
+ export interface AuthRow {
88
+ userKey: string;
89
+ ph2: string | null;
90
+ salt: string | null;
91
+ wrapped: string | null;
92
+ wrappedRec: string | null;
93
+ recHash: string | null;
94
+ /** 訂閱方案(tacet:users.plan,'free'/'paid');兄弟 fork 無訂閱面時預設 'free'。 */
95
+ plan?: string;
96
+ }
97
+ export declare const AUTH_RATE: RateWindow;
98
+ export declare function errResponse(code: string, status: number): Response;
99
+ export declare function loginRouteCore(env: Env & Record<string, unknown>, store: AuthStore, ip: string, ph1In: unknown): Promise<Response>;