leviathan-crypto 1.0.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.
Files changed (78) hide show
  1. package/CLAUDE.md +265 -0
  2. package/LICENSE +21 -0
  3. package/README.md +322 -0
  4. package/SECURITY.md +174 -0
  5. package/dist/chacha.wasm +0 -0
  6. package/dist/chacha20/index.d.ts +49 -0
  7. package/dist/chacha20/index.js +177 -0
  8. package/dist/chacha20/ops.d.ts +16 -0
  9. package/dist/chacha20/ops.js +146 -0
  10. package/dist/chacha20/pool.d.ts +52 -0
  11. package/dist/chacha20/pool.js +188 -0
  12. package/dist/chacha20/pool.worker.d.ts +1 -0
  13. package/dist/chacha20/pool.worker.js +37 -0
  14. package/dist/chacha20/types.d.ts +30 -0
  15. package/dist/chacha20/types.js +1 -0
  16. package/dist/docs/architecture.md +795 -0
  17. package/dist/docs/argon2id.md +290 -0
  18. package/dist/docs/chacha20.md +602 -0
  19. package/dist/docs/chacha20_pool.md +306 -0
  20. package/dist/docs/fortuna.md +322 -0
  21. package/dist/docs/init.md +308 -0
  22. package/dist/docs/loader.md +206 -0
  23. package/dist/docs/serpent.md +914 -0
  24. package/dist/docs/sha2.md +620 -0
  25. package/dist/docs/sha3.md +509 -0
  26. package/dist/docs/types.md +198 -0
  27. package/dist/docs/utils.md +273 -0
  28. package/dist/docs/wasm.md +193 -0
  29. package/dist/embedded/chacha.d.ts +1 -0
  30. package/dist/embedded/chacha.js +2 -0
  31. package/dist/embedded/serpent.d.ts +1 -0
  32. package/dist/embedded/serpent.js +2 -0
  33. package/dist/embedded/sha2.d.ts +1 -0
  34. package/dist/embedded/sha2.js +2 -0
  35. package/dist/embedded/sha3.d.ts +1 -0
  36. package/dist/embedded/sha3.js +2 -0
  37. package/dist/fortuna.d.ts +72 -0
  38. package/dist/fortuna.js +445 -0
  39. package/dist/index.d.ts +13 -0
  40. package/dist/index.js +44 -0
  41. package/dist/init.d.ts +11 -0
  42. package/dist/init.js +49 -0
  43. package/dist/loader.d.ts +4 -0
  44. package/dist/loader.js +30 -0
  45. package/dist/serpent/index.d.ts +65 -0
  46. package/dist/serpent/index.js +242 -0
  47. package/dist/serpent/seal.d.ts +8 -0
  48. package/dist/serpent/seal.js +70 -0
  49. package/dist/serpent/stream-encoder.d.ts +20 -0
  50. package/dist/serpent/stream-encoder.js +167 -0
  51. package/dist/serpent/stream-pool.d.ts +48 -0
  52. package/dist/serpent/stream-pool.js +285 -0
  53. package/dist/serpent/stream-sealer.d.ts +34 -0
  54. package/dist/serpent/stream-sealer.js +223 -0
  55. package/dist/serpent/stream.d.ts +28 -0
  56. package/dist/serpent/stream.js +205 -0
  57. package/dist/serpent/stream.worker.d.ts +32 -0
  58. package/dist/serpent/stream.worker.js +117 -0
  59. package/dist/serpent/types.d.ts +5 -0
  60. package/dist/serpent/types.js +1 -0
  61. package/dist/serpent.wasm +0 -0
  62. package/dist/sha2/hkdf.d.ts +16 -0
  63. package/dist/sha2/hkdf.js +108 -0
  64. package/dist/sha2/index.d.ts +40 -0
  65. package/dist/sha2/index.js +190 -0
  66. package/dist/sha2/types.d.ts +5 -0
  67. package/dist/sha2/types.js +1 -0
  68. package/dist/sha2.wasm +0 -0
  69. package/dist/sha3/index.d.ts +55 -0
  70. package/dist/sha3/index.js +246 -0
  71. package/dist/sha3/types.d.ts +5 -0
  72. package/dist/sha3/types.js +1 -0
  73. package/dist/sha3.wasm +0 -0
  74. package/dist/types.d.ts +24 -0
  75. package/dist/types.js +26 -0
  76. package/dist/utils.d.ts +26 -0
  77. package/dist/utils.js +169 -0
  78. package/package.json +90 -0
@@ -0,0 +1,795 @@
1
+ # Leviathan Crypto Library Architecture
2
+
3
+ > [!NOTE]
4
+ > - Version: 1.0.0
5
+ > - Package: `leviathan-crypto` (npm, unscoped)
6
+ > - Status: v1.0.0 - all four WASM modules (Serpent, ChaCha20, SHA-2, SHA-3) implemented.
7
+ > - Supersedes: `leviathan` (TypeScript reference) and `leviathan-wasm` (WASM primitives),
8
+ > both of which remain unchanged as development references.
9
+
10
+ ## Vision
11
+
12
+ `leviathan-crypto` is a strictly-typed, audited WebAssembly cryptography library for
13
+ the web. It combines two previously separate efforts:
14
+
15
+ - **leviathan:** developer-friendly TypeScript API, strict types, audited against specs
16
+ and known-answer test vectors
17
+ - **leviathan-wasm:** AssemblyScript WASM implementation of the same primitives,
18
+ running outside the JavaScript JIT for predictable execution and practical
19
+ constant-time guarantees
20
+
21
+ The unified library exposes the TypeScript API from leviathan, backed by the WASM
22
+ execution engine from leviathan-wasm. Developers get ergonomic, well-typed classes.
23
+ The runtime gets deterministic cryptographic computation outside the JIT.
24
+
25
+ **The fundamental insight:** JavaScript engines provide no formal constant-time
26
+ guarantees for arbitrary code. WASM execution is deterministic and not subject to
27
+ JIT speculation. For a cryptography library, this distinction matters. The TypeScript
28
+ layer handles API ergonomics; the WASM layer handles all cryptographic computation.
29
+
30
+ ---
31
+
32
+ ## Scope (v1.0)
33
+
34
+ ### In scope
35
+
36
+ | Module | Primitives |
37
+ |--------|-----------|
38
+ | `serpent` | Serpent-256 block cipher: ECB, CTR mode, CBC mode |
39
+ | `serpent` + `sha2` | `SerpentSeal` (Serpent-CBC + HMAC-SHA256), `SerpentStream` / `SerpentStreamPool` (chunked AEAD), `SerpentStreamSealer` / `SerpentStreamOpener` (incremental streaming AEAD) |
40
+ | `chacha20` | ChaCha20, Poly1305, ChaCha20-Poly1305 AEAD, XChaCha20-Poly1305 AEAD |
41
+ | `sha2` | SHA-256, SHA-384, SHA-512, HMAC-SHA256, HMAC-SHA384, HMAC-SHA512, HKDF-SHA256, HKDF-SHA512 |
42
+ | `sha3` | SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256 (XOFs, multi-squeeze) |
43
+
44
+ Pure TypeScript utilities (encoding helpers, random generation, format converters)
45
+ ship alongside the WASM-backed primitives with no `init()` dependency.
46
+
47
+ ### Auxiliary tier (not part of `Module` union)
48
+
49
+ - **`Fortuna`:** CSPRNG requiring two core modules (`serpent` + `sha2`).
50
+ Initialized via the standard `init()` gate.
51
+
52
+ ---
53
+
54
+ ## Repository Structure
55
+
56
+
57
+ ```
58
+ leviathan-crypto/
59
+ ├── src/
60
+ │ ├── asm/ ← AssemblyScript (compiles to .wasm)
61
+ │ │ ├── serpent/
62
+ │ │ │ ├── index.ts ← asc entry point → serpent.wasm
63
+ │ │ │ ├── serpent.ts ← block function + key schedule
64
+ │ │ │ ├── serpent_unrolled.ts ← unrolled S-boxes and round functions
65
+ │ │ │ ├── cbc.ts ← CBC mode
66
+ │ │ │ ├── ctr.ts ← CTR mode
67
+ │ │ │ └── buffers.ts ← static buffer layout + offset getters
68
+ │ │ ├── chacha/
69
+ │ │ │ ├── index.ts ← asc entry point → chacha.wasm
70
+ │ │ │ ├── chacha20.ts
71
+ │ │ │ ├── poly1305.ts
72
+ │ │ │ ├── wipe.ts
73
+ │ │ │ └── buffers.ts
74
+ │ │ ├── sha2/
75
+ │ │ │ ├── index.ts ← asc entry point → sha2.wasm
76
+ │ │ │ ├── sha256.ts
77
+ │ │ │ ├── sha512.ts
78
+ │ │ │ ├── hmac.ts
79
+ │ │ │ ├── hmac512.ts
80
+ │ │ │ └── buffers.ts
81
+ │ │ └── sha3/
82
+ │ │ ├── index.ts ← asc entry point → sha3.wasm
83
+ │ │ ├── keccak.ts
84
+ │ │ └── buffers.ts
85
+ │ └── ts/ ← TypeScript (public API)
86
+ │ ├── init.ts ← initModule() : WASM loading and module cache
87
+ │ ├── loader.ts ← embedded / streaming / manual loading logic
88
+ │ ├── types.ts ← Hash, KeyedHash, Blockcipher, Streamcipher, AEAD
89
+ │ ├── utils.ts ← encoding, wipe, xor, concat, randomBytes
90
+ │ ├── fortuna.ts ← Fortuna CSPRNG (requires serpent + sha2)
91
+ │ ├── embedded/ ← generated base64 files (gitignored, build artifact)
92
+ │ │ ├── serpent.ts
93
+ │ │ ├── chacha.ts
94
+ │ │ ├── sha2.ts
95
+ │ │ └── sha3.ts
96
+ │ ├── serpent/
97
+ │ │ ├── index.ts ← serpentInit() + Serpent, SerpentCtr, SerpentCbc
98
+ │ │ ├── seal.ts ← SerpentSeal (Serpent-CBC + HMAC-SHA256)
99
+ │ │ ├── stream.ts ← SerpentStream (chunked one-shot AEAD)
100
+ │ │ ├── stream-pool.ts ← SerpentStreamPool (Worker-based parallel AEAD)
101
+ │ │ ├── stream-sealer.ts ← SerpentStreamSealer / SerpentStreamOpener (incremental AEAD)
102
+ │ │ ├── stream.worker.ts ← Web Worker entry point for SerpentStreamPool
103
+ │ │ └── types.ts
104
+ │ ├── chacha20/
105
+ │ │ ├── index.ts ← chacha20Init() + ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305
106
+ │ │ ├── pool.ts ← XChaCha20Poly1305Pool + PoolOpts
107
+ │ │ ├── pool.worker.ts ← Web Worker entry point (compiled to pool.worker.js; not a subpath export)
108
+ │ │ └── types.ts
109
+ │ ├── sha2/
110
+ │ │ ├── index.ts ← sha2Init() + SHA256, SHA512, SHA384, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512
111
+ │ │ └── types.ts
112
+ │ ├── sha3/
113
+ │ │ ├── index.ts ← sha3Init() + SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256
114
+ │ │ └── types.ts
115
+ │ └── index.ts ← root barrel : dispatching init() + re-exports everything
116
+ ├── test/
117
+ │ ├── unit/ ← Vitest (JS target, fast iteration)
118
+ │ │ ├── serpent/
119
+ │ │ ├── chacha20/
120
+ │ │ ├── sha2/
121
+ │ │ ├── sha3/
122
+ │ │ └── init.test.ts
123
+ │ ├── e2e/ ← Playwright (WASM target, cross-browser)
124
+ │ └── vectors/ ← test vector files (read-only reference data)
125
+ ├── build/ ← WASM build output (gitignored)
126
+ ├── dist/ ← npm publish output (gitignored)
127
+ ├── docs/ ← project documentation
128
+ ├── scripts/
129
+ │ ├── embed-wasm.ts ← reads build/*.wasm, generates src/ts/embedded/*.ts
130
+ │ ├── build-asm.js ← orchestrates AssemblyScript compilation
131
+ │ ├── gen-seal-vectors.ts ← generates KAT vectors for SerpentSeal / SerpentStream
132
+ │ └── gen-sealstream-vectors.ts ← generates KAT vectors for SerpentStreamSealer / Opener
133
+ ├── package.json
134
+ ├── asconfig.json ← four AssemblyScript entry points
135
+ ├── tsconfig.json
136
+ ├── vitest.config.ts
137
+ └── playwright.config.ts
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Architecture: TypeScript over WASM
143
+
144
+
145
+ > [!NOTE]
146
+ > The TypeScript layer never implements cryptographic algorithms. It handles the
147
+ > JS/WASM boundary: writing inputs into WASM linear memory, calling exported
148
+ > functions, reading outputs back. All algorithm logic lives in AssemblyScript.
149
+ >
150
+ > The exception is the Tier 2 composition layer: `SerpentSeal`, `SerpentStream`,
151
+ > `SerpentStreamPool`, `SerpentStreamSealer`, `SerpentStreamOpener`, and `HKDF`.
152
+ > These are pure TypeScript, they compose the WASM-backed Tier 1 primitives
153
+ > (Serpent-CBC, HMAC-SHA256, HKDF-SHA256) without adding new algorithm logic.
154
+
155
+ ---
156
+
157
+ ## Four Independent WASM Modules
158
+
159
+ Each primitive family compiles to its own `.wasm` binary. Modules are fully
160
+ independent, separate linear memories, separate buffer layouts, no shared state.
161
+
162
+ | Module | Binary | Primitives |
163
+ |--------|--------|------------|
164
+ | `serpent` | `serpent.wasm` | Serpent-256 block cipher: ECB, CTR mode, CBC mode |
165
+ | `chacha20` | `chacha.wasm` | ChaCha20, Poly1305, ChaCha20-Poly1305 AEAD, XChaCha20-Poly1305 AEAD |
166
+ | `sha2` | `sha2.wasm` | SHA-256, SHA-384, SHA-512, HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 |
167
+ | `sha3` | `sha3.wasm` | SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256 |
168
+
169
+ **Benefits:**
170
+ 1. **Size:** consumers who only use Serpent don't load the SHA-3 binary
171
+ 2. **Isolation:** key material in `serpent.wasm` memory cannot bleed into
172
+ `sha3.wasm` memory even in theory
173
+
174
+ Each module's buffer layout starts at offset 0 and is defined in its own
175
+ `buffers.ts`. Buffer layouts are fully independent across modules.
176
+
177
+ ### Module contents
178
+
179
+ **`serpent.wasm`**
180
+ Serpent-256 block cipher. Key schedule, block encrypt, block decrypt. CTR mode
181
+ chunked streaming encrypt/decrypt. CBC mode chunked encrypt/decrypt.
182
+ Source: `src/asm/serpent/`
183
+
184
+ The serpent TypeScript module also includes a Tier 2 composition layer built on
185
+ top of these WASM primitives: `SerpentSeal` (Serpent-CBC + HMAC-SHA256 AEAD),
186
+ `SerpentStream` (chunked one-shot AEAD), `SerpentStreamPool` (Worker-based
187
+ parallel AEAD), and `SerpentStreamSealer` / `SerpentStreamOpener` (incremental
188
+ streaming AEAD). All Tier 2 classes use HKDF-SHA256 for per-chunk key derivation
189
+ and require both `serpent` and `sha2` to be initialized.
190
+
191
+ **`chacha.wasm`**
192
+ ChaCha20 stream cipher (RFC 8439). Poly1305 MAC (RFC 8439 §2.5). ChaCha20-Poly1305
193
+ AEAD (RFC 8439 §2.8). XChaCha20-Poly1305 AEAD (draft-irtf-cfrg-xchacha).
194
+ HChaCha20 subkey derivation.
195
+ Source: `src/asm/chacha/`
196
+
197
+ The chacha20 TypeScript module also includes `pool.ts` (`XChaCha20Poly1305Pool`)
198
+ and `pool.worker.ts`. The worker file compiles to `dist/chacha20/pool.worker.js`
199
+ and ships in the package, but it is **not** registered in the `exports` map, it
200
+ is an internal file loaded by the pool at runtime, not a public named subpath.
201
+
202
+ **`sha2.wasm`**
203
+ SHA-256 and SHA-512 (FIPS 180-4). SHA-384 (SHA-512 with different IVs, truncated
204
+ output, shares all SHA-512 buffers and compress function). HMAC-SHA256,
205
+ HMAC-SHA512, HMAC-SHA384 (RFC 2104). HKDF-SHA256 and HKDF-SHA512 (RFC 5869)
206
+ are pure TypeScript compositions over HMAC, with no new WASM logic.
207
+ Source: `src/asm/sha2/`
208
+
209
+ **`sha3.wasm`**
210
+ Keccak-f[1600] permutation (FIPS 202). SHA3-224, SHA3-256, SHA3-384, SHA3-512.
211
+ SHAKE128, SHAKE256 (XOFs, multi-squeeze capable, unbounded output length).
212
+ All six variants share one permutation, differing only in rate, domain
213
+ separation byte, and output length.
214
+ Source: `src/asm/sha3/`
215
+
216
+ ---
217
+
218
+ ## `init()` API
219
+
220
+ WASM instantiation is async. `init()` is the explicit initialization gate,
221
+ it must be called once before any cryptographic class is used. This is honest
222
+ about the initialization cost and gives the developer control over when it is paid.
223
+
224
+ ### Signature
225
+
226
+ ```typescript
227
+ type Module = 'serpent' | 'chacha20' | 'sha2' | 'sha3'
228
+ type Mode = 'embedded' | 'streaming' | 'manual'
229
+
230
+ interface InitOpts {
231
+ wasmUrl?: URL | string
232
+ wasmBinary?: Record<Module, Uint8Array | ArrayBuffer>
233
+ }
234
+
235
+ async function init(
236
+ modules: Module | Module[],
237
+ mode?: Mode,
238
+ opts?: InitOpts
239
+ ): Promise<void>
240
+ ```
241
+
242
+ ### Three loading modes
243
+
244
+ **`'embedded'` (default: zero-config)**
245
+ The `.wasm` binary is base64-encoded and inlined in the published package as a
246
+ generated TypeScript file (`src/ts/embedded/*.ts`). At runtime, the base64 string
247
+ is decoded and passed to `WebAssembly.instantiate()`. Works everywhere with no
248
+ bundler configuration. ~33% size overhead from base64 encoding. Cannot use
249
+ streaming compilation.
250
+
251
+ ```typescript
252
+ await init(['serpent', 'sha3'])
253
+ ```
254
+
255
+ **`'streaming'` (performance path)**
256
+ Uses `WebAssembly.instantiateStreaming()` for maximum load performance. The
257
+ browser compiles the WASM binary while still downloading it. `wasmUrl` is a
258
+ base URL, the loader appends the filename (`serpent.wasm`, `chacha.wasm`, etc.).
259
+ Requires the `.wasm` files to be served with `Content-Type: application/wasm`.
260
+
261
+ ```typescript
262
+ await init(['serpent', 'sha3'], 'streaming', { wasmUrl: '/assets/wasm/' })
263
+ // loads: /assets/wasm/serpent.wasm, /assets/wasm/sha3.wasm
264
+ ```
265
+
266
+ **`'manual'` (custom loading)**
267
+ Caller provides the compiled binary directly as a `Uint8Array` or `ArrayBuffer`.
268
+ For environments with custom loading requirements (CDN, service worker cache,
269
+ non-standard fetch).
270
+
271
+ ```typescript
272
+ await init(['chacha20'], 'manual', {
273
+ wasmBinary: { chacha20: myBuffer }
274
+ })
275
+ ```
276
+
277
+ ### Behavioral contracts
278
+
279
+ **Idempotent.** Calling `init()` for a module that is already initialized is a
280
+ no-op. Safe to call from multiple modules in a codebase.
281
+
282
+ **Module-scope cache.** The compiled `WebAssembly.Module` for each binary is
283
+ cached at module scope after first compilation. All subsequent class instantiations
284
+ use `WebAssembly.instantiate(cachedModule)`, fast, no recompilation.
285
+
286
+ **Error before init.** Calling any cryptographic class before `init()` throws:
287
+ ```
288
+ leviathan-crypto: call init(['<module>']) before using <ClassName>
289
+ ```
290
+
291
+ **No lazy auto-init.** Classes never silently call `init()` on first use.
292
+ Hidden initialization costs are worse than explicit ones.
293
+
294
+ **Thread safety.** v1.0 uses a single WASM module instance per binary, single
295
+ thread. WASM linear memory is not shared across Workers without `SharedArrayBuffer`
296
+ (which requires COOP/COEP headers). Two pool classes provide Worker-based
297
+ parallelism: `SerpentStreamPool` (chunked authenticated Serpent encryption) and
298
+ `XChaCha20Poly1305Pool` (AEAD). Each pool worker owns its own WASM instances with
299
+ isolated linear memory. For other primitives: create one instance per Worker if
300
+ Workers are used.
301
+
302
+ ---
303
+
304
+ ## Public API Classes
305
+
306
+ Names match conventional cryptographic notation.
307
+
308
+ | Module | Classes |
309
+ |--------|---------|
310
+ | `serpent` + `sha2` | `SerpentSeal`, `SerpentStream`, `SerpentStreamPool`, `SerpentStreamSealer`, `SerpentStreamOpener` |
311
+ | `serpent` | `Serpent`, `SerpentCtr`, `SerpentCbc` |
312
+ | `chacha20` | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, `XChaCha20Poly1305Pool` |
313
+ | `sha2` | `SHA256`, `SHA384`, `SHA512`, `HMAC_SHA256`, `HMAC_SHA384`, `HMAC_SHA512`, `HKDF_SHA256`, `HKDF_SHA512` |
314
+ | `sha3` | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256` |
315
+ | `serpent` + `sha2` | `Fortuna` |
316
+
317
+ HMAC names use underscore separator (`HMAC_SHA256`) matching RFC convention.
318
+ SHA-3 names use underscore separator (`SHA3_256`) for readability.
319
+
320
+ **`Fortuna`** requires `await Fortuna.create()` rather than `new Fortuna()` due
321
+ to the async `init()` dependency on two modules. It requires both `serpent` and
322
+ `sha2` to be initialized. In Node.js, Fortuna collects additional entropy from
323
+ `process.hrtime`, `process.cpuUsage`, `process.memoryUsage`, `os.loadavg`,
324
+ and `os.freemem` in addition to `crypto.randomBytes`.
325
+
326
+ ### Usage pattern
327
+
328
+ All WASM-backed classes follow the same pattern:
329
+
330
+ ```typescript
331
+ import { init, SerpentSeal, SHA3_256 } from 'leviathan-crypto'
332
+
333
+ await init(['serpent', 'sha2', 'sha3'])
334
+
335
+ const seal = new SerpentSeal()
336
+ const ciphertext = seal.encrypt(key, plaintext) // throws on tamper at decrypt
337
+
338
+ const hasher = new SHA3_256()
339
+ const digest = hasher.hash(message)
340
+ ```
341
+
342
+ ### Utility exports (no `init()` required)
343
+
344
+ Pure TypeScript utilities ship alongside the WASM-backed primitives:
345
+
346
+ | Category | Exports |
347
+ |----------|---------|
348
+ | Encoding | `hexToBytes`, `bytesToHex`, `utf8ToBytes`, `bytesToUtf8`, `base64ToBytes`, `bytesToBase64` |
349
+ | Security | `constantTimeEqual`, `wipe`, `xor` |
350
+ | Helpers | `concat`, `randomBytes` |
351
+ | Types | `Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD` |
352
+
353
+ ---
354
+
355
+ ## Build Pipeline
356
+
357
+
358
+ **Step by step:**
359
+
360
+ 1. `npm run build:asm` — AssemblyScript compiler reads `src/asm/*/index.ts`, emits `build/*.wasm`
361
+ 2. `npm run build:embed` — `scripts/embed-wasm.ts` reads each `.wasm`, writes base64 to `src/ts/embedded/*.ts`
362
+ 3. `npm run build:ts` — TypeScript compiler emits `dist/`
363
+ 4. `cp build/*.wasm dist/` — WASM binaries copied for streaming mode consumers
364
+ 5. At runtime (subpath): `serpent/index.ts:serpentInit()` → `initModule()` → `loadEmbedded(thunk)` → `thunk()` → dynamic-import `embedded/serpent.ts` → decode base64 → `WebAssembly.instantiate` → cache in `init.ts`
365
+ 6. At runtime (root): `index.ts:init(['serpent', 'sha3'])` → dispatches to each module's init function (`serpentInit`, `sha3Init`) via `Promise.all` → same path as step 5 per module
366
+
367
+ `src/ts/embedded/` is gitignored, these files are a build artifacts derived from the WASM
368
+ binaries. There is one source of truth for each binary: the AssemblyScript source.
369
+
370
+ ---
371
+
372
+ ## Module Relationship Diagrams
373
+
374
+ ### ASM layer — internal import graph
375
+
376
+ Each WASM module is fully independent. No cross-module imports exist.
377
+
378
+ **Serpent (`src/asm/serpent/`)**
379
+
380
+ ```
381
+ buffers.ts
382
+ <- serpent.ts (offsets for key, block, subkey, work, CBC IV)
383
+ <- serpent_unrolled.ts (block offsets, subkey, work)
384
+ <- cbc.ts (IV, block, chunk offsets)
385
+ <- ctr.ts (nonce, counter, block, chunk offsets)
386
+
387
+ serpent.ts
388
+ <- serpent_unrolled.ts (S-boxes sb0-sb7, si0-si7, lk, kl, keyXor)
389
+
390
+ serpent_unrolled.ts
391
+ <- cbc.ts (encryptBlock_unrolled, decryptBlock_unrolled)
392
+ <- ctr.ts (encryptBlock_unrolled)
393
+
394
+ index.ts
395
+ re-exports: buffers + serpent + serpent_unrolled + cbc + ctr
396
+ ```
397
+
398
+ **ChaCha (`src/asm/chacha/`)**
399
+
400
+ ```
401
+ buffers.ts
402
+ <- chacha20.ts (key, nonce, counter, block, state, poly key, xchacha offsets)
403
+ <- poly1305.ts (poly key, msg, buf, tag, h, r, rs, s offsets)
404
+ <- wipe.ts (all buffer offsets, zeroes everything)
405
+
406
+ index.ts
407
+ re-exports: buffers + chacha20 + poly1305 + wipe
408
+ ```
409
+
410
+ **SHA-2 (`src/asm/sha2/`)**
411
+
412
+ ```
413
+ buffers.ts
414
+ <- sha256.ts (H, block, W, out, input, partial, total offsets)
415
+ <- sha512.ts (H, block, W, out, input, partial, total offsets)
416
+ <- hmac.ts (SHA-256 input, out, ipad, opad, inner offsets)
417
+ <- hmac512.ts (SHA-512 input, out, ipad, opad, inner offsets)
418
+
419
+ sha256.ts
420
+ <- hmac.ts (sha256Init, sha256Update, sha256Final)
421
+
422
+ sha512.ts
423
+ <- hmac512.ts (sha512Init, sha384Init, sha512Update, sha512Final, sha384Final)
424
+
425
+ index.ts
426
+ re-exports: buffers + sha256 + sha512 + hmac + hmac512
427
+ defines: wipeBuffers() inline
428
+ ```
429
+
430
+ **SHA-3 (`src/asm/sha3/`)**
431
+
432
+ ```
433
+ buffers.ts
434
+ <- keccak.ts (state, rate, absorbed, dsbyte, input, out offsets)
435
+
436
+ index.ts
437
+ re-exports: buffers + keccak
438
+ ```
439
+
440
+ ### TS layer — internal import graph
441
+
442
+ ```
443
+ +---------------------+
444
+ | index.ts | <- barrel: dispatching init()
445
+ | (public API root) | + re-exports everything
446
+ +--+--+--+--+--+--+--+
447
+ | | | | | |
448
+ +-------------------------+ | | | | +----------------------+
449
+ | +----------------+ | | +----------+ |
450
+ v v v v v v
451
+ serpent/ chacha20/ sha2/ sha3/ fortuna.ts types.ts
452
+ index.ts index.ts index.ts index.ts utils.ts
453
+ | | | | | | | | |
454
+ | | | | | | | | +-> init.ts (isInitialized)
455
+ | | | +-> utils.ts | | | | +-> serpent/index.ts (Serpent)
456
+ | | | | (constantTime| | | | +-> sha2/index.ts (SHA256)
457
+ | | | | Equal) | | | | +-> utils.ts (wipe, concat,
458
+ | | | | | | | | utf8ToBytes)
459
+ | | | +-> chacha20/ | | | |
460
+ | | | | types.ts | | | |
461
+ | | | | | | | |
462
+ | +----------+--+--+------------+--+----+--+--> init.ts <-- getInstance()
463
+ | | | | | initModule()
464
+ | | | | | isInitialized()
465
+ v v v v v
466
+ embedded/ embedded/ embedded/ embedded/
467
+ serpent.ts chacha.ts sha2.ts sha3.ts
468
+ (each module owns its own embedded thunk, no cross-module imports)
469
+ ```
470
+
471
+ Each module's init function (`serpentInit`, `chacha20Init`, `sha2Init`,
472
+ `sha3Init`) calls `initModule()` from `init.ts`, passing its own embedded thunk. `initModule()` delegates to `loadEmbedded(thunk)` in `loader.ts`.
473
+ The loader calls the thunk, decodes base64, and instantiates the WASM binary.
474
+ `loader.ts` has no knowledge of module names or embedded file paths.
475
+
476
+ ### TS layer — file-by-file imports
477
+
478
+ | File | Imports from | Symbols |
479
+ |------|-------------|---------|
480
+ | `init.ts` | *(none)* | — |
481
+ | `loader.ts` | `init.ts` | `Module` (type) |
482
+ | `types.ts` | *(none)* | — |
483
+ | `utils.ts` | *(none)* | — |
484
+ | `serpent/types.ts` | *(none)* | — |
485
+ | `chacha20/types.ts` | *(none)* | — |
486
+ | `sha2/types.ts` | *(none)* | — |
487
+ | `sha3/types.ts` | *(none)* | — |
488
+ | `serpent/index.ts` | `init.ts`, `embedded/serpent.ts` | `getInstance`, `initModule`, `Mode`, `InitOpts`, `WASM_BASE64` |
489
+ | `serpent/seal.ts` | `serpent/index.ts`, `sha2/index.ts`, `utils.ts` | `SerpentCbc`, `HMAC_SHA256`, `concat`, `constantTimeEqual`, `wipe` |
490
+ | `serpent/stream.ts` | `serpent/index.ts`, `sha2/index.ts`, `utils.ts` | `SerpentCtr`, `HMAC_SHA256`, `HKDF_SHA256`, `constantTimeEqual`, `concat` |
491
+ | `serpent/stream-pool.ts` | `serpent/stream.ts` | `sealChunk`, `openChunk`, `chunkInfo` |
492
+ | `serpent/stream-sealer.ts` | `serpent/index.ts`, `sha2/index.ts`, `utils.ts` | `SerpentCbc`, `HMAC_SHA256`, `HKDF_SHA256`, `concat`, `constantTimeEqual`, `wipe` |
493
+ | `chacha20/index.ts` | `init.ts`, `utils.ts`, `chacha20/types.ts`, `embedded/chacha.ts` | `getInstance`, `initModule`, `Mode`, `InitOpts`, `constantTimeEqual`, `ChaChaExports`, `WASM_BASE64` |
494
+ | `sha2/index.ts` | `init.ts`, `embedded/sha2.ts` | `getInstance`, `initModule`, `Mode`, `InitOpts`, `WASM_BASE64` |
495
+ | `sha3/index.ts` | `init.ts`, `embedded/sha3.ts` | `getInstance`, `initModule`, `Mode`, `InitOpts`, `WASM_BASE64` |
496
+ | `fortuna.ts` | `init.ts`, `serpent/index.ts`, `sha2/index.ts`, `utils.ts` | `isInitialized`, `Serpent`, `SHA256`, `wipe`/`concat`/`utf8ToBytes` |
497
+ | `index.ts` | `serpent/`, `chacha20/`, `sha2/`, `sha3/`, `init.ts`, `fortuna.ts`, `types.ts`, `utils.ts` | `serpentInit`, `chacha20Init`, `sha2Init`, `sha3Init` (from each module), *(all public exports)* |
498
+
499
+ ### TS-to-WASM mapping
500
+
501
+ Each TS wrapper class maps to one WASM module and specific exported functions.
502
+ Tier 2 composition classes (`SerpentSeal`, `SerpentStream*`, `HKDF_*`) are pure
503
+ TypeScript, they call Tier 1 classes rather than WASM functions directly.
504
+
505
+ **serpent/index.ts → asm/serpent/ (Tier 1: direct WASM callers)**
506
+
507
+ | TS Class | WASM functions called |
508
+ |----------|---------------------|
509
+ | `Serpent` | `loadKey`, `encryptBlock`, `decryptBlock`, `wipeBuffers` + buffer getters |
510
+ | `SerpentCtr` | `loadKey`, `resetCounter`, `setCounter`, `encryptChunk`, `decryptChunk`, `wipeBuffers` + buffer getters |
511
+ | `SerpentCbc` | `loadKey`, `cbcEncryptChunk`, `cbcDecryptChunk`, `wipeBuffers` + buffer getters |
512
+
513
+ **serpent/seal.ts, stream.ts, stream-sealer.ts (Tier 2: pure TS composition)**
514
+
515
+ | TS Class | Composes |
516
+ |----------|---------|
517
+ | `SerpentSeal` | `SerpentCbc` + `HMAC_SHA256` |
518
+ | `SerpentStream` | `SerpentCtr` + `HMAC_SHA256` + `HKDF_SHA256` |
519
+ | `SerpentStreamPool` | `SerpentStream` (via worker) |
520
+ | `SerpentStreamSealer` | `SerpentCbc` + `HMAC_SHA256` + `HKDF_SHA256` |
521
+ | `SerpentStreamOpener` | `SerpentCbc` + `HMAC_SHA256` + `HKDF_SHA256` |
522
+
523
+ **chacha20/index.ts → asm/chacha/**
524
+
525
+ | TS Class | WASM functions called |
526
+ |----------|---------------------|
527
+ | `ChaCha20` | `chachaLoadKey`, `chachaSetCounter`, `chachaResetCounter`, `chachaEncryptChunk`, `chachaDecryptChunk`, `wipeBuffers` + buffer getters |
528
+ | `Poly1305` | `polyInit`, `polyUpdate`, `polyFinal`, `wipeBuffers` + buffer getters |
529
+ | `ChaCha20Poly1305` | `chachaLoadKey`, `chachaResetCounter`, `chachaGenPolyKey`, `chachaEncryptChunk`, `chachaDecryptChunk`, `polyInit`, `polyUpdate`, `polyFinal`, `wipeBuffers` + buffer getters |
530
+ | `XChaCha20Poly1305` | All of `ChaCha20Poly1305` + `hchacha20` + xchacha buffer getters |
531
+
532
+ **sha2/index.ts → asm/sha2/**
533
+
534
+ | TS Class | WASM functions called |
535
+ |----------|---------------------|
536
+ | `SHA256` | `sha256Init`, `sha256Update`, `sha256Final`, `wipeBuffers` + buffer getters |
537
+ | `SHA512` | `sha512Init`, `sha512Update`, `sha512Final`, `wipeBuffers` + buffer getters |
538
+ | `SHA384` | `sha384Init`, `sha512Update`, `sha384Final`, `wipeBuffers` + buffer getters |
539
+ | `HMAC_SHA256` | `hmac256Init`, `hmac256Update`, `hmac256Final`, `sha256Init`, `sha256Update`, `sha256Final`, `wipeBuffers` + buffer getters |
540
+ | `HMAC_SHA512` | `hmac512Init`, `hmac512Update`, `hmac512Final`, `sha512Init`, `sha512Update`, `sha512Final`, `wipeBuffers` + buffer getters |
541
+ | `HMAC_SHA384` | `hmac384Init`, `hmac384Update`, `hmac384Final`, `sha384Init`, `sha512Update`, `sha384Final`, `wipeBuffers` + buffer getters |
542
+ | `HKDF_SHA256` | Pure TS composition over `HMAC_SHA256` (extract + expand per RFC 5869) |
543
+ | `HKDF_SHA512` | Pure TS composition over `HMAC_SHA512` (extract + expand per RFC 5869) |
544
+
545
+ **sha3/index.ts → asm/sha3/**
546
+
547
+ | TS Class | WASM functions called |
548
+ |----------|---------------------|
549
+ | `SHA3_224` | `sha3_224Init`, `keccakAbsorb`, `sha3_224Final`, `wipeBuffers` + buffer getters |
550
+ | `SHA3_256` | `sha3_256Init`, `keccakAbsorb`, `sha3_256Final`, `wipeBuffers` + buffer getters |
551
+ | `SHA3_384` | `sha3_384Init`, `keccakAbsorb`, `sha3_384Final`, `wipeBuffers` + buffer getters |
552
+ | `SHA3_512` | `sha3_512Init`, `keccakAbsorb`, `sha3_512Final`, `wipeBuffers` + buffer getters |
553
+ | `SHAKE128` | `shake128Init`, `keccakAbsorb`, `shakePad`, `shakeSqueezeBlock`, `wipeBuffers` + buffer getters |
554
+ | `SHAKE256` | `shake256Init`, `keccakAbsorb`, `shakePad`, `shakeSqueezeBlock`, `wipeBuffers` + buffer getters |
555
+
556
+ ### Cross-module dependencies
557
+
558
+ | Relationship | Notes |
559
+ |-------------|-------|
560
+ | `SerpentSeal`, `SerpentStream`, `SerpentStreamPool`, `SerpentStreamSealer`, `SerpentStreamOpener` → `serpent` + `sha2` | Tier 2 composition: Serpent-CBC/CTR + HMAC-SHA256 + HKDF-SHA256. Both modules must be initialized. |
561
+ | `Fortuna` → `Serpent` + `SHA256` | Only class requiring **two** WASM modules (`serpent` + `sha2`). Uses `Fortuna.create()` static factory instead of `new`. |
562
+ | `XChaCha20Poly1305` → `ChaCha20Poly1305` | Pure TS composition — calls `hchacha20()` for subkey derivation, then delegates to `ChaCha20Poly1305`. |
563
+ | `HKDF_SHA256`, `HKDF_SHA512` → `HMAC_SHA256`, `HMAC_SHA512` | Pure TS composition — extract and expand steps per RFC 5869. |
564
+ | All other classes | Each depends on exactly **one** WASM module. |
565
+
566
+ ### Public API barrel (`src/ts/index.ts`)
567
+
568
+ The root barrel defines and exports the dispatching `init()` function.
569
+ It is the only file that imports all four module-scoped init functions.
570
+
571
+ | Source | Exports |
572
+ |--------|---------|
573
+ | *(barrel itself)* | `init` (dispatching function — calls per-module init functions via `Promise.all`) |
574
+ | `init.ts` | `Module`, `Mode`, `InitOpts`, `isInitialized`, `_resetForTesting` |
575
+ | `serpent/index.ts` | `Serpent`, `SerpentCtr`, `SerpentCbc`, `SerpentSeal`, `SerpentStream`, `SerpentStreamPool`, `SerpentStreamSealer`, `SerpentStreamOpener`, `StreamPoolOpts`, `_serpentReady` |
576
+ | `chacha20/index.ts` | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, `_chachaReady` |
577
+ | `chacha20/pool.ts` | `XChaCha20Poly1305Pool`, `PoolOpts` |
578
+ | `sha2/index.ts` | `SHA256`, `SHA512`, `SHA384`, `HMAC_SHA256`, `HMAC_SHA512`, `HMAC_SHA384`, `HKDF_SHA256`, `HKDF_SHA512`, `_sha2Ready` |
579
+ | `sha3/index.ts` | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256`, `_sha3Ready` |
580
+ | `fortuna.ts` | `Fortuna` |
581
+ | `types.ts` | `Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD` |
582
+ | `utils.ts` | `hexToBytes`, `bytesToHex`, `utf8ToBytes`, `bytesToUtf8`, `base64ToBytes`, `bytesToBase64`, `constantTimeEqual`, `wipe`, `xor`, `concat`, `randomBytes` |
583
+
584
+ Each subpath export also exports its own module-specific init function for
585
+ tree-shakeable loading: `serpentInit(mode?, opts?)`, `chacha20Init(mode?, opts?)`,
586
+ `sha2Init(mode?, opts?)`, `sha3Init(mode?, opts?)`.
587
+
588
+ ---
589
+
590
+ ## npm Package
591
+
592
+ **Subpath exports:**
593
+
594
+ ```json
595
+ {
596
+ "exports": {
597
+ ".": "./dist/index.js",
598
+ "./serpent": "./dist/serpent/index.js",
599
+ "./chacha20": "./dist/chacha20/index.js",
600
+ "./chacha20/pool": "./dist/chacha20/pool.js",
601
+ "./sha2": "./dist/sha2/index.js",
602
+ "./sha3": "./dist/sha3/index.js"
603
+ }
604
+ }
605
+ ```
606
+
607
+ > [!NOTE]
608
+ > `dist/chacha20/pool.worker.js` ships in the package but is not in the
609
+ > `exports` map. It is an internal Web Worker entry point loaded by
610
+ > `XChaCha20Poly1305Pool` at runtime. Do not import it as a named subpath.
611
+
612
+ The root `.` export re-exports everything. Subpath exports allow bundlers to
613
+ tree-shake at the module level, a consumer importing only `./sha3` does not
614
+ include the Serpent wrapper classes or their embedded WASM binaries in their
615
+ bundle.
616
+
617
+ **Tree-shaking:** `"sideEffects": false` is set in `package.json`. Each
618
+ module's `index.ts` owns its own embedded import thunk. Bundlers that support
619
+ tree-shaking (webpack, Rollup, esbuild) can eliminate unused modules and
620
+ their embedded WASM binaries from the final bundle.
621
+
622
+ **Published:** `dist/` only. Contains compiled JS, TypeScript declarations,
623
+ and WASM binaries as assets for streaming mode. The embedded base64 is compiled
624
+ into the JS, not a separate file.
625
+
626
+ **Not published:** `src/`, `test/`, `build/`, `scripts/`, `docs/`
627
+
628
+ ---
629
+
630
+ ## Buffer Layouts
631
+
632
+ All offsets start at 0 per module. Independent linear memory. No offsets are
633
+ shared or coordinated across modules.
634
+
635
+ ### Serpent module — 3 pages (192 KB)
636
+
637
+ Source: `src/asm/serpent/buffers.ts`
638
+
639
+ | Offset | Size | Name |
640
+ |--------|------|------|
641
+ | 0 | 32 | `KEY_BUFFER` — key input (padded to 32 bytes for all key sizes) |
642
+ | 32 | 16 | `BLOCK_PT_BUFFER` — single block plaintext |
643
+ | 48 | 16 | `BLOCK_CT_BUFFER` — single block ciphertext |
644
+ | 64 | 16 | `NONCE_BUFFER` — CTR mode nonce |
645
+ | 80 | 16 | `COUNTER_BUFFER` — 128-bit little-endian counter |
646
+ | 96 | 528 | `SUBKEY_BUFFER` — key schedule output (33 rounds × 4 × 4 bytes) |
647
+ | 624 | 65536 | `CHUNK_PT_BUFFER` — streaming plaintext (CTR/CBC) |
648
+ | 66160 | 65536 | `CHUNK_CT_BUFFER` — streaming ciphertext (CTR/CBC) |
649
+ | 131696 | 20 | `WORK_BUFFER` — 5 × i32 scratch registers (key schedule + S-box/LT rounds) |
650
+ | 131716 | 16 | `CBC_IV_BUFFER` — CBC initialization vector / chaining value |
651
+ | 131732 | — | END |
652
+
653
+ `wipeBuffers()` zeroes all 10 buffers (key, block pt/ct, nonce, counter, subkeys, work, chunk pt/ct, CBC IV).
654
+
655
+ ### ChaCha20 module — 3 pages (192 KB)
656
+
657
+ Source: `src/asm/chacha/buffers.ts`
658
+
659
+ | Offset | Size | Name |
660
+ |--------|------|------|
661
+ | 0 | 32 | `KEY_BUFFER` — ChaCha20 256-bit key |
662
+ | 32 | 12 | `CHACHA_NONCE_BUFFER` — 96-bit nonce (3 × u32, LE) |
663
+ | 44 | 4 | `CHACHA_CTR_BUFFER` — u32 block counter |
664
+ | 48 | 64 | `CHACHA_BLOCK_BUFFER` — 64-byte keystream block output |
665
+ | 112 | 64 | `CHACHA_STATE_BUFFER` — 16 × u32 initial state |
666
+ | 176 | 65536 | `CHUNK_PT_BUFFER` — streaming plaintext |
667
+ | 65712 | 65536 | `CHUNK_CT_BUFFER` — streaming ciphertext |
668
+ | 131248 | 32 | `POLY_KEY_BUFFER` — one-time key r‖s |
669
+ | 131280 | 64 | `POLY_MSG_BUFFER` — message staging (≤ 64 bytes per polyUpdate) |
670
+ | 131344 | 16 | `POLY_BUF_BUFFER` — partial block accumulator |
671
+ | 131360 | 4 | `POLY_BUF_LEN_BUFFER` — u32 bytes in partial block |
672
+ | 131364 | 16 | `POLY_TAG_BUFFER` — 16-byte output MAC tag |
673
+ | 131380 | 40 | `POLY_H_BUFFER` — accumulator h: 5 × u64 |
674
+ | 131420 | 40 | `POLY_R_BUFFER` — clamped r: 5 × u64 |
675
+ | 131460 | 32 | `POLY_RS_BUFFER` — precomputed 5×r[1..4]: 4 × u64 |
676
+ | 131492 | 16 | `POLY_S_BUFFER` — s pad: 4 × u32 |
677
+ | 131508 | 24 | `XCHACHA_NONCE_BUFFER` — full 24-byte XChaCha20 nonce |
678
+ | 131532 | 32 | `XCHACHA_SUBKEY_BUFFER` — HChaCha20 output (key material) |
679
+ | 131564 | — | END |
680
+
681
+ `wipeBuffers()` zeroes all 14 buffer regions (key, chacha nonce/ctr/block/state, chunk pt/ct, poly key/msg/buf/tag/h/r/rs/s, xchacha nonce/subkey).
682
+
683
+ ### SHA-2 module — 3 pages (192 KB)
684
+
685
+ Source: `src/asm/sha2/buffers.ts`
686
+
687
+ | Offset | Size | Name |
688
+ |--------|------|------|
689
+ | 0 | 32 | `SHA256_H` — SHA-256 hash state H0..H7 (8 × u32) |
690
+ | 32 | 64 | `SHA256_BLOCK` — SHA-256 block accumulator |
691
+ | 96 | 256 | `SHA256_W` — SHA-256 message schedule W[0..63] (64 × u32) |
692
+ | 352 | 32 | `SHA256_OUT` — SHA-256 digest output |
693
+ | 384 | 64 | `SHA256_INPUT` — SHA-256 user input staging (one block) |
694
+ | 448 | 4 | `SHA256_PARTIAL` — u32 partial block length |
695
+ | 452 | 8 | `SHA256_TOTAL` — u64 total bytes hashed |
696
+ | 460 | 64 | `HMAC256_IPAD` — HMAC-SHA256 K' XOR ipad |
697
+ | 524 | 64 | `HMAC256_OPAD` — HMAC-SHA256 K' XOR opad |
698
+ | 588 | 32 | `HMAC256_INNER` — HMAC-SHA256 inner hash |
699
+ | 620 | 64 | `SHA512_H` — SHA-512 hash state H0..H7 (8 × u64) |
700
+ | 684 | 128 | `SHA512_BLOCK` — SHA-512 block accumulator |
701
+ | 812 | 640 | `SHA512_W` — SHA-512 message schedule W[0..79] (80 × u64) |
702
+ | 1452 | 64 | `SHA512_OUT` — SHA-512 digest output (SHA-384 uses first 48 bytes) |
703
+ | 1516 | 128 | `SHA512_INPUT` — SHA-512 user input staging (one block) |
704
+ | 1644 | 4 | `SHA512_PARTIAL` — u32 partial block length |
705
+ | 1648 | 8 | `SHA512_TOTAL` — u64 total bytes hashed |
706
+ | 1656 | 128 | `HMAC512_IPAD` — HMAC-SHA512 K' XOR ipad (128-byte block size) |
707
+ | 1784 | 128 | `HMAC512_OPAD` — HMAC-SHA512 K' XOR opad |
708
+ | 1912 | 64 | `HMAC512_INNER` — HMAC-SHA512 inner hash |
709
+ | 1976 | — | END |
710
+
711
+ `wipeBuffers()` zeroes all 20 buffer regions (SHA-256 state/block/W/out/input/partial/total, HMAC-256 ipad/opad/inner, SHA-512 state/block/W/out/input/partial/total, HMAC-512 ipad/opad/inner).
712
+
713
+ ### SHA-3 module — 3 pages (192 KB)
714
+
715
+ Source: `src/asm/sha3/buffers.ts`
716
+
717
+ | Offset | Size | Name |
718
+ |--------|------|------|
719
+ | 0 | 200 | `KECCAK_STATE` — 25 × u64 Keccak-f[1600] lane matrix (5×5, row-major x+5y) |
720
+ | 200 | 4 | `KECCAK_RATE` — u32 rate in bytes (variant-specific: 72–168) |
721
+ | 204 | 4 | `KECCAK_ABSORBED` — u32 bytes absorbed into current block |
722
+ | 208 | 1 | `KECCAK_DSBYTE` — u8 domain separation byte (0x06 for SHA-3, 0x1f for SHAKE) |
723
+ | 209 | 168 | `KECCAK_INPUT` — input staging buffer (max rate = SHAKE128 at 168 bytes) |
724
+ | 377 | 168 | `KECCAK_OUT` — output buffer (one SHAKE128 squeeze block) |
725
+ | 545 | — | END |
726
+
727
+ `wipeBuffers()` zeroes all 6 buffer regions (state, rate, absorbed, dsbyte, input, output).
728
+
729
+ ---
730
+
731
+ ## Test Suite
732
+
733
+ ### Structure
734
+
735
+
736
+ For the full testing methodology and vector corpus, see: [test-suite.md](./test-suite.md)
737
+
738
+ ### Gate discipline
739
+
740
+ Each primitive family has a gate test, the simplest authoritative vector for
741
+ that primitive. The gate must pass before any other tests in that family are
742
+ written or run. Gate tests are annotated with a `// GATE` comment.
743
+
744
+ ### `init.test.ts` contracts
745
+
746
+ - `init()` with each of the three modes loads and caches the module correctly
747
+ - Idempotency: second `init()` call for same module is a no-op
748
+ - Error before init: clear error thrown for each class before its module is loaded
749
+ - Partial init: loading `['serpent']` does not make `sha3` classes available
750
+
751
+ ---
752
+
753
+ ## Correctness Contract
754
+
755
+ leviathan-crypto must produce byte-identical output to the authoritative
756
+ specification for every known test vector. Cross-checks against the leviathan
757
+ TypeScript reference and external tools (OpenSSL, Python hashlib, Node.js crypto)
758
+ provide additional verification layers.
759
+
760
+ The test vector corpus in `test/vectors/` is read-only. Integrity is verified via
761
+ `SHA256SUMS`, expected values are sourced directly from authoritative references.
762
+ They are the immutable truth, and must never be modified to make tests pass.
763
+
764
+ ---
765
+
766
+ ## Known Limitations (v1.0)
767
+
768
+ - **`SerpentCbc` is unauthenticated**: use `SerpentSeal` for authenticated
769
+ Serpent encryption, or pair `SerpentCbc` with `HMAC_SHA256` (Encrypt-then-MAC)
770
+ if direct CBC access is required.
771
+ - **Single-threaded WASM per instance**: one WASM instance per binary per thread.
772
+ `SerpentStreamPool` and `XChaCha20Poly1305Pool` provide Worker-based parallelism
773
+ for their respective AEAD paths; other primitive families remain single-threaded.
774
+ - **Max input per WASM call**: chunk-based APIs (CTR, CBC) accept at most 64KB
775
+ per call. Wrappers handle splitting automatically for larger inputs.
776
+ - **Browser WASM loading**: streaming mode requires files served with
777
+ `Content-Type: application/wasm`. Embedded mode works everywhere.
778
+
779
+ ---
780
+
781
+ ## Cross-References
782
+
783
+ - [README.md](./README.md) — library documentation index, exports table, and quick-start examples
784
+ - [test-suite.md](./test-suite.md) — testing methodology, vector corpus, and gate discipline
785
+ - [init.md](./init.md) — `init()` API, three loading modes, and idempotent behavior
786
+ - [loader.md](./loader.md) — internal WASM binary loading strategies (embedded, streaming, manual)
787
+ - [wasm.md](./wasm.md) — WebAssembly primer: modules, instances, memory, and the init gate
788
+ - [types.md](./types.md) — public TypeScript interfaces (`Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD`)
789
+ - [utils.md](./utils.md) — encoding helpers, `constantTimeEqual`, `wipe`, `randomBytes`
790
+ - [serpent.md](./serpent.md) — Serpent-256 TypeScript API (SerpentSeal, SerpentStream, raw modes)
791
+ - [chacha20.md](./chacha20.md) — ChaCha20/Poly1305 TypeScript API and XChaCha20-Poly1305 AEAD
792
+ - [sha2.md](./sha2.md) — SHA-2 hashes, HMAC, and HKDF TypeScript API
793
+ - [sha3.md](./sha3.md) — SHA-3 hashes and SHAKE XOFs TypeScript API
794
+ - [fortuna.md](./fortuna.md) — Fortuna CSPRNG with forward secrecy and entropy pooling
795
+ - [argon2id.md](./argon2id.md) — Argon2id password hashing and key derivation