@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.
- package/README.md +37 -15
- package/dist/client/argon2.d.ts +166 -0
- package/dist/client/argon2.js +406 -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/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 +60 -16
- package/scripts/verify-core-crypto.ts +88 -11
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": "journal-core-vectors/1",
|
|
3
|
+
"package_version": "0.3.0",
|
|
4
|
+
"spec": "docs/format-spec.md",
|
|
5
|
+
"generator": "scripts/generate-vectors.ts",
|
|
6
|
+
"note": "Frozen evidence generated once by calling the real primitives; not recomputed in CI (random iv/salt). Verify gate [26] checks presence and shape only.",
|
|
7
|
+
"rfc9106": {
|
|
8
|
+
"algorithm": "Argon2id",
|
|
9
|
+
"version": "0x13",
|
|
10
|
+
"memory_kib": 32,
|
|
11
|
+
"iterations": 3,
|
|
12
|
+
"parallelism": 4,
|
|
13
|
+
"tag_len": 32,
|
|
14
|
+
"password_hex": "0101010101010101010101010101010101010101010101010101010101010101",
|
|
15
|
+
"salt_hex": "00000000000000000000000000000000",
|
|
16
|
+
"secret": null,
|
|
17
|
+
"associated_data": null,
|
|
18
|
+
"expected_tag_hex": "72d2a36fd5c266bcc96121b24937bc253338cdfcbd273713655748c54b4dd503"
|
|
19
|
+
},
|
|
20
|
+
"frozen_blobs": [
|
|
21
|
+
{
|
|
22
|
+
"source": "verify [14] KAT14",
|
|
23
|
+
"prefix": "jr2w.",
|
|
24
|
+
"wire": "jr2w.srO0tba3uLm6u7y9vr/AwURFRkdISUpLTE1OTy8+hmx+AhMYCgPVBH1Q1q1UnjPF9uvN/ie4DoYqh4LGkMTHvHDy/Sku/lMRvoRWfuqSuIlX0rEpVU8HfMajsaYtzaz1zHFQELEIuzTsM6+S",
|
|
25
|
+
"payload_len": 108,
|
|
26
|
+
"inputs": {
|
|
27
|
+
"passphrase": "correct-horse-battery-staple-42",
|
|
28
|
+
"pin": "482913",
|
|
29
|
+
"salt1_hex": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
|
|
30
|
+
"pinSaltPrefix": "tacet-note-pin1:",
|
|
31
|
+
"hkdf_info": "journal-kek2-v1:jr2w."
|
|
32
|
+
},
|
|
33
|
+
"expected_note_key_hex": "c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"source": "verify [17] KAT17",
|
|
37
|
+
"prefix": "jr3w.",
|
|
38
|
+
"wire": "jr3w.BeQ8F6s3DdJWY853l2jRv5dHtVEMd+tjJ3XuIisu2ymuK7IInuqIvxtwPD3WzhTkEtCRr+h7KOfWo5FM6ELGjPxwxHAp5LtM5zcEg8hmCqH3dMX9iHF7IOQl3Ak=",
|
|
39
|
+
"payload_len": 92,
|
|
40
|
+
"inputs": {
|
|
41
|
+
"passphrase": "PASS12x",
|
|
42
|
+
"salt_hex": "f6a511fb29b131869d282d89f9eaa90a"
|
|
43
|
+
},
|
|
44
|
+
"expected_note_key_hex": "c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"source": "verify [17] KAT17",
|
|
48
|
+
"prefix": "jr3d.",
|
|
49
|
+
"wire": "jr3d.nOB7P29euSEa+LM8qMSTRe6r32TbBX7nS2R/WyRJ7N1NO+mqXuW5u9HLiLzYObKHq3NURwiEaxD9qwli646qDoG4/5wlHD7UE9nBT485oUeEx/vYIkmOu4muJQ42avl8zX9M1PSgaP+Bk+R9",
|
|
50
|
+
"payload_len": 108,
|
|
51
|
+
"inputs": {
|
|
52
|
+
"passphrase": "dual-factor-passphrase-42",
|
|
53
|
+
"pin": "2580ab",
|
|
54
|
+
"salt1_hex": "a06bd4b1f293b8c8ebff4b6a02f13a34",
|
|
55
|
+
"pinSalt3Prefix": "tacet-note-pin3:",
|
|
56
|
+
"hkdf_info": "journal-kek2-v1:jr3d."
|
|
57
|
+
},
|
|
58
|
+
"expected_note_key_hex": "c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"source": "verify [17] KAT17",
|
|
62
|
+
"prefix": "jr4w.",
|
|
63
|
+
"wire": "jr4w.x6cZmk7UVUAf8abE3mLps22+BLTdWGTDOZpTs4DqHFUDUcz5p5VpdIqYub/9r+sE2/hYE1fPb68ABSCdDeEvJVXlGOsbDr9L//xsdFcZZYlRtrMO3APSwJQBrv0=",
|
|
64
|
+
"payload_len": 92,
|
|
65
|
+
"inputs": {
|
|
66
|
+
"passphrase": "v3-generation-passphrase-42",
|
|
67
|
+
"salt_hex": "31a8d4ae616ccc0a6def28a9b0db4860"
|
|
68
|
+
},
|
|
69
|
+
"expected_note_key_hex": "c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"source": "verify [17] KAT17",
|
|
73
|
+
"prefix": "jr4d.",
|
|
74
|
+
"wire": "jr4d.Dz8wgY5Lphif69Fh5fE+54J4YBlUJgfVLHBeM7RGr3HUmplys5FpcIK/AZT8zstvsUSpox2mwnpo4eQA9eqtbRK3kTowaWRyAEjTfx4kJS9eMoPQEWsr+GRRYgtdINlgAoVJxyi/NaCNqhDR",
|
|
75
|
+
"payload_len": 108,
|
|
76
|
+
"inputs": {
|
|
77
|
+
"passphrase": "dual-factor-passphrase-42",
|
|
78
|
+
"pin": "2580ab",
|
|
79
|
+
"salt1_hex": "d6ebccb6c9bb3a0d13905183d2b2977d",
|
|
80
|
+
"pinSalt3Prefix": "tacet-note-pin3:",
|
|
81
|
+
"hkdf_info": "journal-kek2-v1:jr4d."
|
|
82
|
+
},
|
|
83
|
+
"expected_note_key_hex": "c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"source": "verify [20]",
|
|
87
|
+
"prefix": "jr1r.",
|
|
88
|
+
"wire": "jr1r.bkvBFdKjNVfidHcfn0WoyyRJLkajWhXF7WmPdc3Q9FCNNxDz+7PgN0x25BjuAZPFi1oP3t+8dblrnSQEltkieBh33xoyChcGKe1wEfaStQF6ITBNb6vBZKVXDEQ=",
|
|
89
|
+
"payload_len": 92,
|
|
90
|
+
"inputs": {
|
|
91
|
+
"rec_token": "62ff0221e30b56740ec210cd36df2e9667841585c6d0fcd5200826b965aa36a5",
|
|
92
|
+
"identity": "kat-id-0000000000000000",
|
|
93
|
+
"recKekHkdf": "jr1r.",
|
|
94
|
+
"recKekSalt": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
|
|
95
|
+
"hkdf_info": "journal-kek-rec-v1:jr1r."
|
|
96
|
+
},
|
|
97
|
+
"expected_note_key_hex": "57c72afacd486194c41227f42311600e287a7d48baa7a3e9de32fa460c089699"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"source": "verify [20]",
|
|
101
|
+
"prefix": "jr1w.",
|
|
102
|
+
"wire": "jr1w.a4xchJ+EPui8iQy5dKpQyGjalu0nlJMsDd0LaLyRrY3mt+6112Se4NvaibymRFHsMg1LgOYsBVy15t0CiU1ipf9g8rniqBmdf1R5LzVXQdNJDCzbAfubPUKc/FE=",
|
|
103
|
+
"payload_len": 92,
|
|
104
|
+
"inputs": {
|
|
105
|
+
"rec_token": "62ff0221e30b56740ec210cd36df2e9667841585c6d0fcd5200826b965aa36a5",
|
|
106
|
+
"identity": "kat-id-0000000000000000",
|
|
107
|
+
"recSaltPrefix": "tacet-note-rec1:"
|
|
108
|
+
},
|
|
109
|
+
"expected_note_key_hex": "57c72afacd486194c41227f42311600e287a7d48baa7a3e9de32fa460c089699"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"credentials": [
|
|
113
|
+
{
|
|
114
|
+
"kind": "ph1_legacy",
|
|
115
|
+
"input": "correct-horse-battery-staple-42",
|
|
116
|
+
"salt": null,
|
|
117
|
+
"expected_hex": "7dd095c64aba71ab6c386f3a01508e411007fc3855561a7bdb07c62d9fb98801"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"kind": "ph1_v2",
|
|
121
|
+
"input": "correct-horse-battery-staple-42",
|
|
122
|
+
"salt": "tacet-ph1-v1",
|
|
123
|
+
"expected_hex": "dc7c78fe83a0b3a84eace16c404c80300a10f93f3235ad5c7d563d5e1aab5baf"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"kind": "ph1_v3",
|
|
127
|
+
"input": "v3-generation-passphrase-42",
|
|
128
|
+
"input_nfkc": "v3-generation-passphrase-42",
|
|
129
|
+
"salt": "tacet-ph1-v2",
|
|
130
|
+
"expected_hex": "0d2f022c5e42c710e981feb484157ae35d7cf9542b489ae35b99c8fee001eae6"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"kind": "ph2_of_ph1_v2",
|
|
134
|
+
"input": "dc7c78fe83a0b3a84eace16c404c80300a10f93f3235ad5c7d563d5e1aab5baf",
|
|
135
|
+
"salt": null,
|
|
136
|
+
"expected_hex": "5860adc190682591654ebb81ca980c08b76a7ea50b2e0191610522caabcdf537"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"kind": "rec_token_hash",
|
|
140
|
+
"input": "62ff0221e30b56740ec210cd36df2e9667841585c6d0fcd5200826b965aa36a5",
|
|
141
|
+
"salt": null,
|
|
142
|
+
"expected_hex": "f700df2e385610d8295db7f69a6ac3481c456e6f88ec2dee8e44f8e8161f59bd"
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tacet-ink/journal-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Zero-knowledge journal core — e2e crypto, passphrase-only auth, transport, ratelimit. The server never sees plaintext.",
|
|
5
5
|
"homepage": "https://tacet.ink",
|
|
6
6
|
"repository": {
|
|
@@ -25,27 +25,69 @@
|
|
|
25
25
|
"privacy",
|
|
26
26
|
"tacet"
|
|
27
27
|
],
|
|
28
|
-
"main": "
|
|
29
|
-
"types": "
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"exports": {
|
|
32
|
-
".":
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"./client/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"./
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"./
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./client/note-crypto": {
|
|
37
|
+
"types": "./dist/client/note-crypto.d.ts",
|
|
38
|
+
"default": "./dist/client/note-crypto.js"
|
|
39
|
+
},
|
|
40
|
+
"./client/keys": {
|
|
41
|
+
"types": "./dist/client/keys.d.ts",
|
|
42
|
+
"default": "./dist/client/keys.js"
|
|
43
|
+
},
|
|
44
|
+
"./client/argon2": {
|
|
45
|
+
"types": "./dist/client/argon2.d.ts",
|
|
46
|
+
"default": "./dist/client/argon2.js"
|
|
47
|
+
},
|
|
48
|
+
"./client/pinlock": {
|
|
49
|
+
"types": "./dist/client/pinlock.d.ts",
|
|
50
|
+
"default": "./dist/client/pinlock.js"
|
|
51
|
+
},
|
|
52
|
+
"./client/bip39": {
|
|
53
|
+
"types": "./dist/client/bip39.d.ts",
|
|
54
|
+
"default": "./dist/client/bip39.js"
|
|
55
|
+
},
|
|
56
|
+
"./client/wordlist": {
|
|
57
|
+
"types": "./dist/client/wordlist.d.ts",
|
|
58
|
+
"default": "./dist/client/wordlist.js"
|
|
59
|
+
},
|
|
60
|
+
"./server/auth": {
|
|
61
|
+
"types": "./dist/server/auth.d.ts",
|
|
62
|
+
"default": "./dist/server/auth.js"
|
|
63
|
+
},
|
|
64
|
+
"./server/ratelimit": {
|
|
65
|
+
"types": "./dist/server/ratelimit.d.ts",
|
|
66
|
+
"default": "./dist/server/ratelimit.js"
|
|
67
|
+
},
|
|
68
|
+
"./server/cors": {
|
|
69
|
+
"types": "./dist/server/cors.d.ts",
|
|
70
|
+
"default": "./dist/server/cors.js"
|
|
71
|
+
},
|
|
72
|
+
"./server/hash": {
|
|
73
|
+
"types": "./dist/server/hash.d.ts",
|
|
74
|
+
"default": "./dist/server/hash.js"
|
|
75
|
+
},
|
|
76
|
+
"./server/env": {
|
|
77
|
+
"types": "./dist/server/env.d.ts",
|
|
78
|
+
"default": "./dist/server/env.js"
|
|
79
|
+
},
|
|
80
|
+
"./server/ladder": {
|
|
81
|
+
"types": "./dist/server/ladder.d.ts",
|
|
82
|
+
"default": "./dist/server/ladder.js"
|
|
83
|
+
}
|
|
45
84
|
},
|
|
46
85
|
"files": [
|
|
86
|
+
"dist",
|
|
87
|
+
"docs",
|
|
47
88
|
"src",
|
|
48
89
|
"scripts",
|
|
90
|
+
"!scripts/generate-vectors.ts",
|
|
49
91
|
"migrations",
|
|
50
92
|
"README.md",
|
|
51
93
|
"LICENSE"
|
|
@@ -54,6 +96,8 @@
|
|
|
54
96
|
"node": ">=24.7.0"
|
|
55
97
|
},
|
|
56
98
|
"scripts": {
|
|
99
|
+
"build": "tsc -p tsconfig.build.json",
|
|
100
|
+
"prepublishOnly": "npm run build",
|
|
57
101
|
"verify": "node --experimental-strip-types scripts/verify-core-crypto.ts",
|
|
58
102
|
"typecheck": "tsc -p ."
|
|
59
103
|
},
|
|
@@ -142,15 +142,16 @@ async function A(name: string, cond: boolean | Promise<boolean>, detail = ''): P
|
|
|
142
142
|
// 帳面契約:--only 態總帳顯形「本輪 X/選段帳 N(N 案非本輪載)」+OK 標記帶 (--ONLY …) 尾碼;skip 段帳面恆列「未跑非通過」;[24]/[25] runner 自證段隨每輪在場。
|
|
143
143
|
// 段標記 helper(secOpen):全跑態輸出位元組恆等原 console.log('\n[N] …');--only 態僅選中段輸出段頭。
|
|
144
144
|
// 毒化矩陣內層 rerun(execFileSync+POISON_GATE_INNER=1)不傳遞 --only=內層恆全帳(POISON_GATE_INNER 語意不變)。
|
|
145
|
-
// 段帳=執行帳(healthy-repo 全跑 runtime 真值,2026-10-10 實測
|
|
145
|
+
// 段帳=執行帳(healthy-repo 全跑 runtime 真值,2026-10-10 實測 492——A2 [26] +8);帳面語意契約(逐段「案 X/帳」
|
|
146
146
|
// 對 secRan 執行值顯形):執行 < 帳 的段=本環境條件態未行使(非 skip 態),--only 期望算術對齊本帳。
|
|
147
147
|
// [23] 執行 18 帳 21=[23]④ lookup 未配置(0.2.3 單查恆等)/[23]⑤ fail-open(表缺席)兩情境
|
|
148
|
-
// 在本機樹由 [8]
|
|
148
|
+
// 在本機樹由 [8](登入憑證 Argon2id 派生)內登入 lane 的訊號面真行使(try/catch 異常面)——車道分流非帳面虛報。
|
|
149
149
|
// secEnter 跑行為脫鉤 secCases;secCases 鍵只承載段存在/帳面/--only 判準的登記義務
|
|
150
150
|
//(secOpen(22,…) 無段標記行=慣例 skip 段)。缺帳防線(teeth):secRan 實跑段必已登記——
|
|
151
|
-
// for (const [k] of secRan) if (!secCases[k] && k !== 22) FAIL
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
// for (const [k] of secRan) if (!secCases[k] && k !== 22) FAIL(負向斷言本體在 [25] 段
|
|
152
|
+
//——secCases 全摘即 RED);反向缺席段=帳面誠實「案 0/N · skip」。
|
|
153
|
+
// secCases 載體三群:[24]/[25] self-account+--only 段存在面+teeth(段帳由 secRan 執行自記)。
|
|
154
|
+
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 };
|
|
154
155
|
function parseOnly(spec: string): { err: string | null; set: number[] } {
|
|
155
156
|
const set = new Set<number>();
|
|
156
157
|
const specStr = spec.trim();
|
|
@@ -2524,7 +2525,7 @@ secOpen(21, '[21] CI 供應鏈面(npm ci --ignore-scripts+dependabot runner
|
|
|
2524
2525
|
await A('[21] publish.yml:npm ci --ignore-scripts(tag 驅動無 actor 面;typecheck+verify 先行=腳本面零在場)',
|
|
2525
2526
|
srcPy21.includes('npm ci --ignore-scripts'));
|
|
2526
2527
|
} else {
|
|
2527
|
-
await A('[21] CI 源碼面:.github 缺席環境(tarball 發行樹常態)=顯性 SKIP(workflow 斷言 4 收斂為 1 SKIP 行——消費端計數帳
|
|
2528
|
+
await A('[21] CI 源碼面:.github 缺席環境(tarball 發行樹常態)=顯性 SKIP(workflow 斷言 4 收斂為 1 SKIP 行——消費端計數帳 489)',
|
|
2528
2529
|
true, 'no .github dir — consumption tree face');
|
|
2529
2530
|
}
|
|
2530
2531
|
// 摘除自證(t_760f44e8 治理掃蕩補漏;fs 原語缺席環境=顯性 FAIL 形自守衛——非 silent true)
|
|
@@ -2789,6 +2790,82 @@ secOpen(23, '[23] loginRouteCore ladder 查表守衛(外審 #8 幽靈帳——
|
|
|
2789
2790
|
}
|
|
2790
2791
|
|
|
2791
2792
|
};
|
|
2793
|
+
// ── 26. 格式規格+測試向量(docs/format-spec.md+docs/vectors/*.json;0.3.0 批 A2) ──
|
|
2794
|
+
//
|
|
2795
|
+
// 單向契約:源碼 → scripts/generate-vectors.ts → JSON(凍結證據,commit 入 repo)→ 本段只檢存在+shape。
|
|
2796
|
+
// 本段不依賴產生器、不重算 KDF(向量含隨機 iv/鹽=重算即漂移);BIP39 是確定性轉寫,逐組對照現行原語。
|
|
2797
|
+
// teeth:payload 位元組長度=b64 解碼真值、fields 拼接=payload 逐位元組、布局長度=規格表列、
|
|
2798
|
+
// 欄位名逐一在規格檔、凍結 blob 逐字=本閘 KAT 字面([14]/[17]/[20])、RFC 9106 期望值=argon2.ts 常數。
|
|
2799
|
+
secOpen(26, '[26] 格式規格+測試向量(docs/format-spec.md+docs/vectors 存在性+shape)'); if (secEnter(26)) {
|
|
2800
|
+
{
|
|
2801
|
+
const readJson26 = async (rel: string): Promise<Record<string, unknown> | null> => {
|
|
2802
|
+
try { return JSON.parse(await srcOf(rel)) as Record<string, unknown>; } catch { return null; }
|
|
2803
|
+
};
|
|
2804
|
+
const spec26 = await srcOf('../docs/format-spec.md').catch(() => '');
|
|
2805
|
+
const fam26 = await readJson26('../docs/vectors/families.json');
|
|
2806
|
+
const kat26 = await readJson26('../docs/vectors/kat.json');
|
|
2807
|
+
const bip26 = await readJson26('../docs/vectors/bip39.json');
|
|
2808
|
+
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 }[] };
|
|
2809
|
+
const vecs26 = (Array.isArray(fam26?.vectors) ? fam26!.vectors : []) as Vec26[];
|
|
2810
|
+
const HEAD26 = ['format', 'package_version', 'spec', 'generator', 'note'];
|
|
2811
|
+
await A('[26] 規格檔+三向量檔在場且 JSON parse;頂層共通欄齊(format=journal-core-vectors/1、spec/generator 路徑字面——產生器不被閘依賴,發行包不帶產生器)',
|
|
2812
|
+
spec26.length > 0 && !!fam26 && !!kat26 && !!bip26
|
|
2813
|
+
&& [fam26, kat26, bip26].every((j) => HEAD26.every((k) => typeof j![k] === 'string') && j!.format === 'journal-core-vectors/1'
|
|
2814
|
+
&& j!.spec === 'docs/format-spec.md' && j!.generator === 'scripts/generate-vectors.ts'));
|
|
2815
|
+
// 規格表前綴集(§1 家族逐條表:| `prefix` | 家族 | 布局 | payload | KDF | AAD |)
|
|
2816
|
+
const specRows26 = spec26.split('\n').filter((l) => /^\| `jr[0-9a-z]{2}\.`(([^|]*))? \|/.test(l) && /\| (content|salt-external|salt-embedded) \|/.test(l));
|
|
2817
|
+
const specPrefixes26 = new Set(specRows26.map((l) => l.match(/^\| `(jr[0-9a-z]{2}\.)`/)![1]));
|
|
2818
|
+
const vecPrefixes26 = new Set(vecs26.map((v) => v.prefix));
|
|
2819
|
+
await A('[26] 家族覆蓋雙向:規格家族表 16 列前綴集=families.json 前綴集(15 前綴;jr1w. 兩世代各一組)',
|
|
2820
|
+
specRows26.length === 16 && vecs26.length === 16 && specPrefixes26.size === 15
|
|
2821
|
+
&& [...specPrefixes26].every((p) => vecPrefixes26.has(p)) && [...vecPrefixes26].every((p) => specPrefixes26.has(p)));
|
|
2822
|
+
const VKEYS26 = ['prefix', 'family', 'layout', 'kdf', 'aad', 'inputs', 'wire', 'payload_len', 'fields'];
|
|
2823
|
+
await A('[26] families 位元組 shape:payload_len=wire b64 解碼真長;fields offset 連續、len 總和=payload_len、hex 拼接=payload 逐位元組',
|
|
2824
|
+
vecs26.length > 0 && vecs26.every((v) => {
|
|
2825
|
+
if (!VKEYS26.every((k) => k in v) || !v.wire.startsWith(v.prefix)) return false;
|
|
2826
|
+
let payload: Uint8Array;
|
|
2827
|
+
try { payload = unb64Mod(v.wire.slice(v.prefix.length)); } catch { return false; }
|
|
2828
|
+
let off = 0;
|
|
2829
|
+
for (const f of v.fields) { if (f.offset !== off || f.hex.length !== f.len * 2) return false; off += f.len; }
|
|
2830
|
+
return payload.length === v.payload_len && off === v.payload_len && v.fields.map((f) => f.hex).join('') === hex(payload);
|
|
2831
|
+
}));
|
|
2832
|
+
await A('[26] 布局長度=規格:salt-external 92/salt-embedded 108(pinSalt 頭 16)/content 28+UTF8(plaintext);且規格家族表該前綴列含同布局+同長度',
|
|
2833
|
+
vecs26.length > 0 && vecs26.every((v) => {
|
|
2834
|
+
const want = v.layout === 'salt-external' ? 92 : v.layout === 'salt-embedded' ? 108 : v.layout === 'content' ? 28 + enc.encode(String(v.inputs.plaintext)).length : -1;
|
|
2835
|
+
const lenCol = v.layout === 'content' ? '28+n' : String(want);
|
|
2836
|
+
const head = v.layout === 'salt-embedded' ? v.fields[0]?.name === 'pinSalt' && v.fields[0]?.len === 16 : v.fields[0]?.name === 'iv';
|
|
2837
|
+
return want === v.payload_len && head && specRows26.some((l) => l.startsWith('| `' + v.prefix + '`') && l.includes('| ' + v.layout + ' | ' + lenCol + ' |'));
|
|
2838
|
+
}));
|
|
2839
|
+
const KKEYS26 = ['rfc9106', 'frozen_blobs', 'credentials', 'expected_tag_hex', 'expected_note_key_hex', 'expected_hex', 'samples', 'entropy_hex', 'checksum_hex', 'words', 'source'];
|
|
2840
|
+
await A('[26] 欄位名對規格:families 每筆欄名+fields 子欄(name/offset/len/hex)+kat/bip39 欄名,逐一以 `欄名` 形出現在 format-spec.md',
|
|
2841
|
+
spec26.length > 0 && [...HEAD26, ...VKEYS26, 'name', 'offset', 'len', 'hex', ...KKEYS26].every((k) => spec26.includes('`' + k + '`'))
|
|
2842
|
+
&& KKEYS26.slice(0, 3).every((k) => !!kat26 && k in kat26) && !!bip26 && 'samples' in bip26);
|
|
2843
|
+
const gate26 = await srcOf('./verify-core-crypto.ts');
|
|
2844
|
+
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 }[];
|
|
2845
|
+
const rfc26 = (kat26?.rfc9106 ?? {}) as Record<string, unknown>;
|
|
2846
|
+
const creds26 = (Array.isArray(kat26?.credentials) ? kat26!.credentials : []) as { kind: string; expected_hex: string }[];
|
|
2847
|
+
await A('[26] kat 對帳:RFC 9106 expected_tag_hex=ARGON_RFC9106_EXPECTED(argon2.ts 常數);凍結 blob 7 筆逐字=本閘 KAT 字面([14]/[17]/[20])且 payload_len=解碼真長;憑證樣本 5 筆 hex64',
|
|
2848
|
+
(await import('../src/client/argon2.ts')).ARGON_RFC9106_EXPECTED === rfc26.expected_tag_hex
|
|
2849
|
+
&& frozen26.length === 7 && frozen26.every((b) => gate26.includes("'" + b.wire + "'") && b.wire.startsWith(b.prefix)
|
|
2850
|
+
&& unb64Mod(b.wire.slice(b.prefix.length)).length === b.payload_len && /^[0-9a-f]{64}$/.test(b.expected_note_key_hex))
|
|
2851
|
+
&& creds26.length === 5 && creds26.every((c) => /^[0-9a-f]{64}$/.test(c.expected_hex)));
|
|
2852
|
+
const samples26 = (Array.isArray(bip26?.samples) ? bip26!.samples : []) as { entropy_hex: string; checksum_hex: string; words: string[]; source: string }[];
|
|
2853
|
+
await A('[26] bip39 抽樣 ≥32 組:entropy hex64+24 詞+checksum=SHA-256(entropy)[0];逐組對照現行轉寫原語(recTokenToWords 逐詞相等——確定性轉寫非 KDF 重算)',
|
|
2854
|
+
samples26.length >= 32 && (await (async () => {
|
|
2855
|
+
for (const s of samples26) {
|
|
2856
|
+
if (!/^[0-9a-f]{64}$/.test(s.entropy_hex) || s.words?.length !== 24) return false;
|
|
2857
|
+
const cs = new Uint8Array(await crypto.subtle.digest('SHA-256', hexToBytes(s.entropy_hex) as BufferSource))[0];
|
|
2858
|
+
if (cs.toString(16).padStart(2, '0') !== s.checksum_hex) return false;
|
|
2859
|
+
if ((await bip.recTokenToWords(s.entropy_hex))?.join(' ') !== s.words.join(' ')) return false;
|
|
2860
|
+
}
|
|
2861
|
+
return true;
|
|
2862
|
+
})()));
|
|
2863
|
+
const pkg26 = JSON.parse(await srcOf('../package.json')) as { files?: string[] };
|
|
2864
|
+
await A('[26] 發行面:package.json files 含 docs(tarball 帶規格+向量——消費端可對帳)+產生器排除(!scripts/generate-vectors.ts——[22] scripts 封閉集 2 檔不動)',
|
|
2865
|
+
Array.isArray(pkg26.files) && pkg26.files.includes('docs') && pkg26.files.includes('!scripts/generate-vectors.ts'));
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2792
2869
|
secOpen(24, '[24] runner 契約自證(--only 解析契約 fail-closed+段級帳面顯形)'); if (secEnter(24)) {
|
|
2793
2870
|
await A('[24] --only 解析契約(全跑態):spec 缺席=onlySet 空集=secEnter 恆真(CI 形契約不變——npm run verify 全帳;--only 態本面恆真跳過=帳面由次面承載)',
|
|
2794
2871
|
!onlyMode() ? (onlySpecRaw === null && parseOnly('').err === 'ERR_ONLY_SPEC_EMPTY' && secEnter(1) && secEnter(23)) : true);
|
|
@@ -2809,19 +2886,19 @@ secOpen(24, '[24] runner 契約自證(--only 解析契約 fail-closed+段級
|
|
|
2809
2886
|
}
|
|
2810
2887
|
|
|
2811
2888
|
secOpen(25, '[25] 段級運行樣本:secEnter 真值表+tag 形帳+secCases 段帳'); if (secEnter(25)) {
|
|
2812
|
-
await A('[25] secEnter 真值表:bannerless 段全跑態行為(
|
|
2813
|
-
!onlyMode() ? (secEnter(0) === false && (secRan.get(
|
|
2889
|
+
await A('[25] secEnter 真值表:bannerless 段全跑態行為(27 未在 secOrder 段帳=secRan 零位面——帳面逐段只列 secOrder;n=0 恆 false);全跑態實段 1/15/23 全真',
|
|
2890
|
+
!onlyMode() ? (secEnter(0) === false && (secRan.get(27) ?? 0) === 0 && secEnter(1) && secEnter(15) && secEnter(23)) : true);
|
|
2814
2891
|
await A('[25] tag/spec 形帳:--only 態 (--ONLY …) 括形+spec 遞增 CSV 非空;全跑態 spec 空+tag 裸',
|
|
2815
2892
|
onlyMode() ? (onlyTag().startsWith('(--ONLY ') && onlyTag().endsWith(')') && onlySpecDisp().length > 0)
|
|
2816
2893
|
: (onlySpecDisp() === '' && onlyTag() === ''));
|
|
2817
2894
|
await A('[25] secCases 段帳登記義務(負向斷言——teeth):secRan 實跑段(本機樹 [23]⑤ fail-open 表缺席異常面照行)必已登記;secCases 缺鍵且非 22 段=RED(secCases 全摘禁令的行為面承載)',
|
|
2818
2895
|
(() => { 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] ===
|
|
2896
|
+
await A('[25] self-account:secCases[25] = 4 恰此四案(段帳面 self-proving——帳漂移即本面 RED;secCases 載體面:[24]/[25] 自證段+--only 段存在面+teeth 三載體)',
|
|
2897
|
+
secCases[25] === 4 && (secRan.get(25) ?? 0) === 3);
|
|
2821
2898
|
}
|
|
2822
2899
|
|
|
2823
2900
|
// ── 段級帳面(誠實帳——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];
|
|
2901
|
+
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];
|
|
2825
2902
|
const secTotal = secOrder.reduce((a, n) => a + (secCases[n] ?? 0), 0);
|
|
2826
2903
|
const ranSecs = secOrder.filter((n) => (secRan.get(n) ?? 0) > 0);
|
|
2827
2904
|
const selTotal = secOrder.filter((n) => !onlyMode() || onlySet.has(n) || n === 24 || n === 25).reduce((a, n) => a + (secCases[n] ?? 0), 0);
|