leviathan-crypto 2.0.1 → 2.1.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 (93) hide show
  1. package/CLAUDE.md +171 -7
  2. package/LICENSE +4 -0
  3. package/README.md +109 -54
  4. package/SECURITY.md +125 -238
  5. package/dist/chacha20/cipher-suite.d.ts +10 -0
  6. package/dist/chacha20/cipher-suite.js +65 -2
  7. package/dist/chacha20/generator.d.ts +12 -0
  8. package/dist/chacha20/generator.js +91 -0
  9. package/dist/chacha20/index.d.ts +97 -1
  10. package/dist/chacha20/index.js +139 -11
  11. package/dist/chacha20/ops.d.ts +57 -6
  12. package/dist/chacha20/ops.js +93 -13
  13. package/dist/chacha20/pool-worker.js +12 -0
  14. package/dist/chacha20/types.d.ts +1 -32
  15. package/dist/ct-wasm.js +1 -1
  16. package/dist/ct.wasm +0 -0
  17. package/dist/docs/aead.md +66 -26
  18. package/dist/docs/architecture.md +600 -521
  19. package/dist/docs/argon2id.md +17 -14
  20. package/dist/docs/chacha20.md +146 -39
  21. package/dist/docs/exports.md +46 -10
  22. package/dist/docs/fortuna.md +339 -122
  23. package/dist/docs/init.md +24 -25
  24. package/dist/docs/loader.md +142 -47
  25. package/dist/docs/serpent.md +139 -41
  26. package/dist/docs/sha2.md +77 -19
  27. package/dist/docs/sha3.md +81 -15
  28. package/dist/docs/types.md +155 -15
  29. package/dist/docs/utils.md +171 -81
  30. package/dist/embedded/chacha20-pool-worker.d.ts +1 -0
  31. package/dist/embedded/chacha20-pool-worker.js +5 -0
  32. package/dist/embedded/kyber.d.ts +1 -1
  33. package/dist/embedded/kyber.js +1 -1
  34. package/dist/embedded/serpent-pool-worker.d.ts +1 -0
  35. package/dist/embedded/serpent-pool-worker.js +5 -0
  36. package/dist/fortuna.d.ts +14 -8
  37. package/dist/fortuna.js +144 -50
  38. package/dist/index.d.ts +8 -6
  39. package/dist/index.js +6 -5
  40. package/dist/init.d.ts +0 -2
  41. package/dist/init.js +83 -3
  42. package/dist/kyber/indcpa.js +4 -4
  43. package/dist/kyber/index.js +25 -5
  44. package/dist/kyber/kem.js +56 -1
  45. package/dist/kyber/suite.d.ts +1 -2
  46. package/dist/kyber/types.d.ts +1 -0
  47. package/dist/kyber/validate.d.ts +8 -4
  48. package/dist/kyber/validate.js +18 -14
  49. package/dist/kyber.wasm +0 -0
  50. package/dist/loader.d.ts +7 -2
  51. package/dist/loader.js +25 -28
  52. package/dist/ratchet/index.d.ts +6 -0
  53. package/dist/ratchet/index.js +37 -0
  54. package/dist/ratchet/kdf-chain.d.ts +13 -0
  55. package/dist/ratchet/kdf-chain.js +85 -0
  56. package/dist/ratchet/ratchet-keypair.d.ts +9 -0
  57. package/dist/ratchet/ratchet-keypair.js +61 -0
  58. package/dist/ratchet/root-kdf.d.ts +4 -0
  59. package/dist/ratchet/root-kdf.js +124 -0
  60. package/dist/ratchet/skipped-key-store.d.ts +14 -0
  61. package/dist/ratchet/skipped-key-store.js +154 -0
  62. package/dist/ratchet/types.d.ts +36 -0
  63. package/dist/ratchet/types.js +26 -0
  64. package/dist/serpent/cipher-suite.d.ts +10 -0
  65. package/dist/serpent/cipher-suite.js +135 -50
  66. package/dist/serpent/generator.d.ts +12 -0
  67. package/dist/serpent/generator.js +97 -0
  68. package/dist/serpent/index.d.ts +61 -1
  69. package/dist/serpent/index.js +92 -7
  70. package/dist/serpent/pool-worker.js +25 -101
  71. package/dist/serpent/serpent-cbc.d.ts +14 -4
  72. package/dist/serpent/serpent-cbc.js +50 -32
  73. package/dist/serpent/shared-ops.d.ts +83 -0
  74. package/dist/serpent/shared-ops.js +213 -0
  75. package/dist/serpent/types.d.ts +1 -5
  76. package/dist/sha2/hash.d.ts +2 -0
  77. package/dist/sha2/hash.js +53 -0
  78. package/dist/sha2/index.d.ts +1 -0
  79. package/dist/sha2/index.js +15 -1
  80. package/dist/sha3/hash.d.ts +2 -0
  81. package/dist/sha3/hash.js +53 -0
  82. package/dist/sha3/index.d.ts +17 -2
  83. package/dist/sha3/index.js +79 -7
  84. package/dist/stream/header.js +5 -5
  85. package/dist/stream/open-stream.js +36 -14
  86. package/dist/stream/seal-stream-pool.d.ts +1 -0
  87. package/dist/stream/seal-stream-pool.js +38 -8
  88. package/dist/stream/seal-stream.js +29 -11
  89. package/dist/types.d.ts +21 -0
  90. package/dist/utils.d.ts +7 -8
  91. package/dist/utils.js +73 -40
  92. package/dist/wasm-source.d.ts +9 -8
  93. package/package.json +79 -64
@@ -1,293 +1,425 @@
1
- # Architecture
2
1
 
3
- > [!NOTE]
4
- > `leviathan-crypto` v2.0 packages five WASM modules (Serpent, ChaCha20, SHA-2, SHA-3, Kyber), generic streaming AEAD via CipherSuite, and worker pool parallelism. It supersedes `leviathan` (TypeScript reference) and `leviathan-wasm` (WASM primitives), both of which remain unchanged as development references.
2
+ ### Architecture
3
+
4
+ Overview of Leviathan Crypto's architecture design, comprising six independent WASM modules unified by a misuse-resistant TypeScript API, which delivers both Serpent's paranoia and ChaCha's elegance as a zero-dependency, tree-shakable, post-quantum library.
5
5
 
6
6
  > ### Table of Contents
7
+ >
7
8
  > - [Vision](#vision)
8
9
  > - [Scope](#scope)
9
10
  > - [Repository Structure](#repository-structure)
10
11
  > - [Architecture: TypeScript over WASM](#architecture-typescript-over-wasm)
11
- > - [Five Independent WASM Modules](#five-independent-wasm-modules)
12
+ > - [Six Independent WASM Modules](#six-independent-wasm-modules)
12
13
  > - [init() API](#init-api)
13
14
  > - [Public API Classes](#public-api-classes)
14
15
  > - [Build Pipeline](#build-pipeline)
15
- > - [Module Relationship Diagrams](#module-relationship-diagrams)
16
+ > - [Module Relationships](#module-relationships)
16
17
  > - [npm Package](#npm-package)
17
18
  > - [Buffer Layouts](#buffer-layouts)
18
19
  > - [Test Suite](#test-suite)
19
20
  > - [Correctness Contract](#correctness-contract)
20
21
  > - [Known Limitations](#known-limitations)
21
22
 
22
- ---
23
-
24
23
  ## Vision
25
24
 
26
- `leviathan-crypto` is a strictly-typed, audited WebAssembly cryptography library for
27
- the web. It combines two previously separate efforts:
25
+ `leviathan-crypto` is a post-quantum WASM cryptography library with zero dependencies, tree-shakable, and side-effect free.
26
+
27
+ **JS is the problem, SIMD WASM is the solution.** JavaScript engines offer no formal constant-time guarantees. JIT compilers optimize based on runtime patterns, which leak secrets through cache access and instruction timing. By contrast, [WebAssembly](https://github.com/xero/leviathan-crypto/wiki/wasm) executes outside the JIT entirely, running compiled bytecode with linear memory you control. No speculative optimization, no value-dependent branches between source and execution.
28
28
 
29
- **leviathan.** Developer-friendly TypeScript API, strict types, audited against specs and known-answer test vectors.
29
+ **WebAssembly is the correctness layer.** All algorithm logic lives in WASM. Six AssemblyScript modules ([`serpent`](https://github.com/xero/leviathan-crypto/wiki/asm_serpent), [`chacha20`](https://github.com/xero/leviathan-crypto/wiki/asm_chacha), [`sha2`](https://github.com/xero/leviathan-crypto/wiki/asm_sha2), [`sha3`](https://github.com/xero/leviathan-crypto/wiki/asm_sha3), [`kyber`](https://github.com/xero/leviathan-crypto/wiki/asm_kyber), and [`ct`](https://github.com/xero/leviathan-crypto/wiki/asm_ct)) compile independently to WASM with SIMD where it pays off. Each module is its own instance with its own linear memory. Within a module, stateful primitives share the instance, and a runtime exclusivity model keeps them from interfering with each other.
30
30
 
31
- **leviathan-wasm.** AssemblyScript WASM implementation of the same primitives, running outside the JavaScript JIT for predictable execution and practical constant-time guarantees.
31
+ **TypeScript is the ergonomics layer.** The strongly-typed public API covers [`Seal`](https://github.com/xero/leviathan-crypto/wiki/aead#seal), [`SealStream`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstream), [`SealStreamPool`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstreampool), [`Fortuna`](https://github.com/xero/leviathan-crypto/wiki/fortuna), [`HKDF`](https://github.com/xero/leviathan-crypto/wiki/sha2#hkdf_sha256), [`SkippedKeyStore`](https://github.com/xero/leviathan-crypto/wiki/ratchet#skippedkeystore), and others. The design is misuse-resistant by default. Authentication is verify-then-decrypt; key material wipes on dispose; validation runs before any crypto path; one-shot AEADs lock on first call. TypeScript never implements cryptographic algorithms. It orchestrates the WASM layer and enforces best practice through API shape, not convention.
32
32
 
33
- The unified library exposes the TypeScript API from leviathan, backed by the WASM
34
- execution engine from leviathan-wasm. Developers get ergonomic, well-typed classes.
35
- The runtime gets deterministic cryptographic computation outside the JIT.
33
+ **[Serpent-256](https://github.com/xero/leviathan-crypto/wiki/serpent_reference): maximum paranoia.** 32 rounds of S-boxes in pure Boolean logic with no table lookups. An ouroboros devouring every bit, in every block, through every round.
36
34
 
37
- **The fundamental insight:** JavaScript engines provide no formal constant-time
38
- guarantees for arbitrary code. WASM execution is deterministic and not subject to
39
- JIT speculation. For a cryptography library, this distinction matters. The TypeScript
40
- layer handles API ergonomics; the WASM layer handles all cryptographic computation.
35
+ **[XChaCha20-Poly1305](https://github.com/xero/leviathan-crypto/wiki/chacha_reference): precise elegance.** 20 rounds of add-rotate-XOR, choreography without S-boxes or cache-timing leakage. A dance closing with Poly1305's unconditional forgery bound.
36
+
37
+ **Two ciphers, one interface.** Both share the [`CipherSuite`](https://github.com/xero/leviathan-crypto/wiki/ciphersuite) shape and slot into [`Seal`](https://github.com/xero/leviathan-crypto/wiki/aead#seal), [`SealStream`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstream), and [`SealStreamPool`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstreampool) interchangeably. Post-quantum extends the same model, [`KyberSuite`](https://github.com/xero/leviathan-crypto/wiki/ciphersuite#kybersuite) wraps [`MlKem512`](https://github.com/xero/leviathan-crypto/wiki/kyber#parameter-sets), [`MlKem768`](https://github.com/xero/leviathan-crypto/wiki/kyber#parameter-sets), or [`MlKem1024`](https://github.com/xero/leviathan-crypto/wiki/kyber#parameter-sets) around either cipher, and the [SPQR ratchet](https://github.com/xero/leviathan-crypto/wiki/ratchet) builds forward-secret sessions on top.
41
38
 
42
39
  ---
43
40
 
44
41
  ## Scope
45
42
 
46
- ### In scope
43
+ **Primitives.** WASM algorithms with their TypeScript wrapper classes.
44
+
45
+ | Module | Algorithms | TypeScript API |
46
+ | ----------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
47
+ | [`serpent`](./asm_serpent.md) | Serpent-256 block cipher: ECB, CTR, CBC | [`Serpent`](./serpent.md) (cipher class for mode operations) |
48
+ | [`chacha20`](./asm_chacha.md) | ChaCha20, Poly1305, ChaCha20-Poly1305, XChaCha20-Poly1305 | [`ChaCha20`](./chacha20.md#chacha20), [`Poly1305`](./chacha20.md#poly1305), [`XChaCha20Poly1305`](./chacha20.md#xchacha20poly1305) |
49
+ | [`sha2`](./asm_sha2.md) | SHA-256, SHA-384, SHA-512, HMAC variants, HKDF variants | [`SHA256`](./sha2.md#sha256), [`SHA384`](./sha2.md#sha384), [`SHA512`](./sha2.md#sha512), [`HMAC_SHA256`](./sha2.md#hmac_sha256), [`HMAC_SHA384`](./sha2.md#hmac_sha384), [`HMAC_SHA512`](./sha2.md#hmac_sha512), [`HKDF_SHA256`](./sha2.md#hkdf_sha256), [`HKDF_SHA512`](./sha2.md#hkdf_sha512) |
50
+ | [`sha3`](./asm_sha3.md) | SHA3-224/256/384/512, SHAKE128, SHAKE256 (XOFs) | [`SHA3_256`](./sha3.md#sha3_256), [`SHA3_512`](./sha3.md#sha3_512), [`SHAKE256`](./sha3.md#shake256) |
51
+ | [`kyber`](./asm_kyber.md) | MlKem512, MlKem768, MlKem1024 | [`MlKem512`](./kyber.md#parameter-sets), [`MlKem768`](./kyber.md#parameter-sets), [`MlKem1024`](./kyber.md#parameter-sets) |
52
+ | [`ct`](./asm_ct.md) | Constant-time comparison primitives | [`constantTimeEqual`](./utils.md#constanttimeequal) |
53
+
54
+
55
+ **Cipher Suites.** Composition of WASM modules into complete cipher packages.
56
+
57
+ | Suite | Composition | Use case |
58
+ | ------------------------------------- | ------------------------------------ | ----------------------------------- |
59
+ | [`SerpentCipher`](./ciphersuite.md#serpentcipher) | `serpent` + `sha2` (CBC+HMAC-SHA256) | Authenticated encryption via STREAM |
60
+ | [`XChaCha20Cipher`](./ciphersuite.md#xchacha20cipher) | `chacha20` (XChaCha20-Poly1305 AEAD) | Streaming authenticated encryption |
61
+ | [`KyberSuite`](./ciphersuite.md#kybersuite) | `kyber` + (any cipher) | Post-quantum key encapsulation |
47
62
 
48
- | Module | Primitives |
49
- | ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
50
- | `serpent` | Serpent-256 block cipher: ECB, CTR mode, CBC mode |
51
- | `serpent` + `sha2` | `SerpentCipher` (CipherSuite for STREAM construction: CBC+HMAC-SHA256) |
52
- | `chacha20` | ChaCha20, Poly1305, ChaCha20-Poly1305 AEAD, XChaCha20-Poly1305 AEAD, `XChaCha20Cipher` (CipherSuite for streaming AEAD) |
53
- | `sha2` | SHA-256, SHA-384, SHA-512, HMAC-SHA256, HMAC-SHA384, HMAC-SHA512, HKDF-SHA256, HKDF-SHA512 |
54
- | `sha3` / `keccak` | SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256 (XOFs, multi-squeeze). `'keccak'` is an alias for `'sha3'`; same binary, same instance slot. |
55
- | `kyber` | `MlKem512`, `MlKem768`, `MlKem1024`. Requires `sha3` for Keccak sponge operations. |
56
- | `stream` | `SealStream`, `OpenStream` (cipher-agnostic STREAM construction), `SealStreamPool` (worker-based parallelism) |
63
+ **High-Level Constructs.** Pure TypeScript abstractions over cipher suites.
57
64
 
58
- Pure TypeScript utilities (encoding helpers, random generation, format converters)
59
- ship alongside the WASM-backed primitives with no `init()` dependency.
65
+ | API | Dependencies | Purpose |
66
+ | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- | -------------------------------------------- |
67
+ | [`Seal`](./aead.md#seal) / [`SealStream`](./aead.md#sealstream) / [`SealStreamPool`](./aead.md#sealstreampool) | Any CipherSuite | One-shot, streaming, and parallel encryption |
68
+ | [`ratchetInit`](./ratchet.md#ratchetinitsk-context), [`KDFChain`](./ratchet.md#kdfchain), [`kemRatchetEncap`](./ratchet.md#kemratchetencapkem-rk-peerek-context)/[`kemRatchetDecap`](./ratchet.md#kemratchetdecapkem-rk-dk-kemct-ownek-context) | `sha2`; `kyber` + `sha3` for KEM | Forward-secret session ratcheting (SPQR) |
69
+ | [`Fortuna`](./fortuna.md) | Cipher PRF + HashFn | Cryptographically-secure RNG |
60
70
 
61
- ### Auxiliary tier (not part of `Module` union)
71
+ **Utilities.** Pure TypeScript helpers, no `init()` dependency.
62
72
 
63
- - **`Fortuna`:** CSPRNG requiring two core modules (`serpent` + `sha2`).
73
+ | Utility | Purpose |
74
+ | --------------------------------------------------- | ------------------------------------------------ |
75
+ | [`hexToBytes`](./utils.md#hextobytes), [`bytesToHex`](./utils.md#bytestohex) | Hex/byte conversions |
76
+ | [`wipe`](./utils.md#wipe) | Secure memory zeroing |
77
+ | [`xor`](./utils.md#xor), [`concat`](./utils.md#concat) | Byte operations |
78
+ | [`randomBytes`](./utils.md#randombytes) | One-off random byte generation |
79
+ | [`constantTimeEqual`](./utils.md#constanttimeequal) | Timing-attack resistant comparison (WASM-backed) |
64
80
 
65
81
  ---
66
82
 
67
83
  ## Repository Structure
68
84
 
69
- ```text
70
- leviathan-crypto/
71
- ├── .github/
72
- │ └── workflows/ ← CI: build, test-suite, e2e, publish, release, wiki
73
- ├── src/
74
- │ ├── asm/ ← AssemblyScript (compiles to .wasm)
75
- │ │ ├── serpent/
76
- │ │ │ ├── index.ts ← asc entry point serpent.wasm
77
- │ │ │ ├── serpent.ts ← block function + key schedule
78
- │ │ │ ├── serpent_unrolled.ts unrolled S-boxes and round functions
79
- │ │ │ ├── serpent_simd.ts ← SIMD bitsliced block operations
80
- │ │ │ ├── cbc.ts ← CBC mode
81
- │ │ │ ├── cbc_simd.ts ← SIMD CBC decrypt
82
- │ │ │ ├── ctr.ts ← CTR mode
83
- │ │ │ ├── ctr_simd.ts ← SIMD CTR 4-wide inter-block
84
- │ │ │ └── buffers.ts ← static buffer layout + offset getters
85
- │ │ ├── chacha20/
86
- │ │ │ ├── index.ts ← asc entry point → chacha20.wasm
87
- │ │ │ ├── chacha20.ts
88
- │ │ │ ├── chacha20_simd_4x.ts ← SIMD 4-wide inter-block ChaCha20
89
- │ │ │ ├── poly1305.ts
90
- │ │ │ ├── wipe.ts
91
- │ │ │ └── buffers.ts
92
- │ │ ├── sha2/
93
- │ │ │ ├── index.ts ← asc entry point → sha2.wasm
94
- │ │ │ ├── sha256.ts
95
- │ │ │ ├── sha512.ts
96
- │ │ │ ├── hmac.ts
97
- │ │ │ ├── hmac512.ts
98
- │ │ │ └── buffers.ts
99
- │ │ ├── sha3/
100
- │ │ ├── index.ts ← asc entry point → sha3.wasm
101
- │ │ ├── keccak.ts
102
- │ │ └── buffers.ts
103
- │ │ ├── ct/
104
- │ │ │ └── index.ts ← asc entry point → ct.wasm (SIMD constant-time compare)
105
- │ │ └── kyber/
106
- │ │ ├── index.ts ← asc entry point → kyber.wasm
107
- │ │ ├── ntt.ts ← NTT/invNTT (scalar reference + zetas table)
108
- │ │ ├── ntt_simd.ts ← SIMD NTT/invNTT (v128 butterflies, fqmul_8x, barrett_reduce_8x)
109
- │ │ ├── reduce.ts ← Montgomery/Barrett reduction, fqmul
110
- │ │ ├── poly.ts ← polynomial serialization, compression, arithmetic, basemul
111
- │ │ ├── poly_simd.ts ← SIMD poly add/sub/reduce/ntt wrappers
112
- │ │ ├── polyvec.ts ← k-wide polyvec operations
113
- │ │ ├── cbd.ts ← centered binomial distribution (η=2, η=3)
114
- │ │ ├── sampling.ts ← uniform rejection sampling
115
- │ │ ├── verify.ts ← constant-time compare and conditional move
116
- │ │ ├── params.ts Q, QINV, MONT, Barrett/compression constants
117
- │ │ └── buffers.ts ← static buffer layout + offset getters
118
- │ └── ts/ TypeScript (public API)
119
- │ ├── init.ts ← initModule() : WASM loading and module cache
120
- │ ├── loader.ts loadWasm() / compileWasm() : polymorphic WasmSource dispatch
121
- │ ├── wasm-source.ts ← WasmSource union type
122
- │ ├── errors.ts ← AuthenticationError
123
- │ ├── types.ts ← Hash, KeyedHash, Blockcipher, Streamcipher, AEAD
124
- ├── utils.ts ← encoding, wipe, xor, concat, randomBytes
125
- ├── fortuna.ts ← Fortuna CSPRNG (requires serpent + sha2)
126
- ├── embedded/ ← generated gzip+base64 blobs (build artifact, gitignored)
127
- │ │ ├── serpent.ts
128
- │ │ ├── chacha20.ts
129
- │ │ ├── sha2.ts
130
- │ │ └── sha3.ts
131
- ├── serpent/
132
- │ │ ├── index.ts ← serpentInit() + Serpent, SerpentCtr, SerpentCbc
133
- │ │ ├── cipher-suite.ts ← SerpentCipher (CipherSuite for STREAM construction)
134
- │ │ ├── pool-worker.ts ← Web Worker for SealStreamPool with SerpentCipher
135
- │ │ ├── embedded.ts ← re-exports gzip+base64 blob as named export
136
- │ │ └── types.ts
137
- │ ├── chacha20/
138
- │ │ ├── index.ts ← chacha20Init() + ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher
139
- │ │ ├── ops.ts ← raw AEAD functions shared by classes and pool worker
140
- │ │ ├── cipher-suite.ts ← XChaCha20Cipher (CipherSuite for STREAM construction)
141
- │ │ ├── pool-worker.ts ← Web Worker for SealStreamPool with XChaCha20Cipher
142
- │ │ ├── embedded.ts re-exports gzip+base64 blob as named export
143
- │ │ └── types.ts
144
- │ ├── sha2/
145
- │ │ ├── index.ts ← sha2Init() + SHA256, SHA512, SHA384, HMAC, HKDF
146
- │ │ ├── hkdf.ts ← HKDF_SHA256, HKDF_SHA512 (pure TS over HMAC)
147
- ├── embedded.ts ← re-exports gzip+base64 blob as named export
148
- └── types.ts
149
- ├── sha3/
150
- ├── index.ts sha3Init() + SHA3_224–512, SHAKE128, SHAKE256
151
- ├── embedded.ts re-exports gzip+base64 blob as named export
152
- └── types.ts
153
- ├── kyber/
154
- ├── index.ts kyberInit() + MlKem512, MlKem768, MlKem1024, KyberSuite
155
- │ │ ├── kem.ts ← Fujisaki-Okamoto transform (keygen, encaps, decaps)
156
- ├── suite.ts ← KyberSuite factory (hybrid KEM+AEAD CipherSuite)
157
- ├── indcpa.ts IND-CPA encrypt/decrypt + matrix generation
158
- ├── validate.ts key validation (FIPS 203 §7.2, §7.3)
159
- ├── params.ts parameter sets (MLKEM512, MLKEM768, MLKEM1024)
160
- ├── types.ts KyberExports, Sha3Exports, KEM API types
161
- └── embedded.ts re-exports gzip+base64 blob as kyberWasm
162
- ├── stream/
163
- ├── index.ts barrel: Seal, SealStream, OpenStream, SealStreamPool, constants
164
- ├── seal.ts Seal (static one-shot AEAD)
165
- ├── seal-stream.ts SealStream (cipher-agnostic streaming encryption)
166
- ├── open-stream.ts OpenStream (cipher-agnostic streaming decryption)
167
- ├── seal-stream-pool.ts ← SealStreamPool (worker-based parallel batch)
168
- │ │ ├── header.ts ← wire format header encode/decode, counter nonce
169
- ├── constants.ts ← HEADER_SIZE, CHUNK_MIN/MAX, TAG_DATA/FINAL, FLAG_FRAMED
170
- └── types.ts CipherSuite, DerivedKeys, SealStreamOpts
171
- └── index.ts root barrel : dispatching init() + re-exports everything
172
- ├── test/
173
- │ ├── unit/ Vitest (JS target, fast iteration)
174
- ├── serpent/
175
- ├── chacha20/
176
- ├── sha2/
177
- │ ├── sha3/
178
- │ │ ├── stream/ ← SealStream, OpenStream, SealStreamPool tests
179
- ├── loader/ ← WasmSource loading tests
180
- ├── init.test.ts
181
- ├── errors.test.ts
182
- ├── fortuna.test.ts
183
- │ └── utils.test.ts
184
- ├── e2e/ ← Playwright (WASM target, cross-browser)
185
- └── vectors/ ← test vector files (read-only reference data)
186
- ├── scripts/
187
- ├── build-asm.js orchestrates AssemblyScript compilation
188
- │ ├── embed-wasm.ts ← reads build/*.wasm, generates src/ts/embedded/*.ts
189
- │ ├── gen-seal-vectors.ts ← generates KAT vectors for Seal
190
- │ ├── gen-sealstream-vectors.ts ← generates KAT vectors for SealStream
191
- │ ├── generate_simd.ts ← generates SIMD assembly variants
192
- │ ├── gen-changelog.ts ← changelog generation
193
- │ ├── copy-docs.ts ← copies docs subset to dist/
194
- │ └── pin-actions.ts ← pins GitHub Actions to SHA hashes
195
- ├── docs/ ← project documentation + wiki source
85
+
86
+ Source lives under `src/`, split between AssemblyScript primitives in `src/asm/` and the TypeScript API in `src/ts/`. Tests are in `test/`. Build, codegen, and tooling scripts go in `scripts/`. CI/CD configuration sits in `.github/`. The repository root holds project documentation, package metadata, and tool configs. Each subsection below shows the relevant tree and notes the conventions that apply across files in that tier.
87
+
88
+ ### CI/CD
89
+
90
+ `.github/` holds GitHub-specific repository configuration: workflow definitions, the CI image build context, and platform metadata. Workflows split along functional lines.
91
+
92
+ **Merge gate.** `build.yml`, `lint.yml`, `e2e.yml`, `test-suite.yml`. `test-suite.yml` orchestrates the per-domain unit runners (`unit-*.yml`) plus `verify-vectors.yml` for parallel execution and per-domain failure isolation.
93
+
94
+ **Test vectors.** `verify-vectors.yml` validates the corpus against `SHA256SUMS`.
95
+
96
+ **Release flow.** Manual `release.yml` bumps the version and creates the tag; the resulting `v*` tag push triggers `publish.yml`, which runs the npm publish with provenance attestations. `npm-remove.yml` is the manual deprecate/unpublish escape hatch.
97
+
98
+ **Wiki.** `wiki.yml` syncs `docs/` to the GitHub Wiki on every merge to main.
99
+
100
+ **CI image.** `ci-image.yml` rebuilds the test-runner container from `ci.Dockerfile` whenever the Dockerfile changes.
101
+
102
+ ```
103
+ .github/
104
+ ├── ci.Dockerfile
105
+ └── workflows/
106
+ ├── build.yml
107
+ ├── ci-image.yml
108
+ ├── e2e.yml
109
+ ├── lint.yml
110
+ ├── npm-remove.yml
111
+ ├── publish.yml
112
+ ├── release.yml
113
+ ├── test-suite.yml
114
+ ├── unit-chacha20.yml
115
+ ├── unit-core.yml
116
+ ├── unit-hashing.yml
117
+ ├── unit-kyber.yml
118
+ ├── unit-montecarlo-cbc.yml
119
+ ├── unit-montecarlo-ecb.yml
120
+ ├── unit-nessie.yml
121
+ ├── unit-ratchet.yml
122
+ ├── unit-serpent.yml
123
+ ├── unit-stream.yml
124
+ ├── verify-vectors.yml
125
+ └── wiki.yml
126
+ ```
127
+
128
+ ### Build pipeline
129
+
130
+ `scripts/` holds the build, codegen, and tooling scripts that produce `dist/` and the test-vector corpus. Three categories.
131
+
132
+ **Build orchestration.** `build-asm.js` drives AssemblyScript compilation across the six modules. `embed-wasm.ts` produces the gzip+base64 blob for each `.wasm`. `embed-workers.ts` bundles each pool worker into a self-contained IIFE via esbuild. `copy-docs.ts` ships the consumer doc subset into `dist/`. See [Build Pipeline](#build-pipeline) for the full sequence.
133
+
134
+ **Codegen.** `generate_simd.ts` produces `src/asm/serpent/serpent_simd.ts` from a template by translating S-box gate logic into v128 ops; the generator and its output are both committed and the output is never edited by hand. `gen-seal-vectors.ts`, `gen-sealstream-vectors.ts`, `gen-fortuna-vectors.ts`, and `gen-ratchet-vectors.ts` produce known-answer-test vectors for their respective primitives.
135
+
136
+ **Tooling.** `gen-changelog.ts` generates `CHANGELOG` entries. `lint-asm.js` lints the AssemblyScript sources. `pin-actions.ts` pins every GitHub Action reference to a SHA, run via `bun pin` after workflow changes.
137
+
138
+ ```
139
+ scripts/
140
+ ├── build-asm.js
141
+ ├── copy-docs.ts
142
+ ├── embed-wasm.ts
143
+ ├── embed-workers.ts
144
+ ├── gen-changelog.ts
145
+ ├── gen-fortuna-vectors.ts
146
+ ├── gen-ratchet-vectors.ts
147
+ ├── gen-seal-vectors.ts
148
+ ├── gen-sealstream-vectors.ts
149
+ ├── generate_simd.ts
150
+ ├── lint-asm.js
151
+ └── pin-actions.ts
152
+ ```
153
+
154
+ ### AssemblyScript layer
155
+
156
+ `src/asm/` holds the AssemblyScript sources for each WASM binary. Every subdirectory compiles to its own `.wasm` with fully independent linear memory and no cross-module imports.
157
+
158
+ **Per-module conventions.** Every module exposes an `index.ts` as the asc entry point; it re-exports the public surface that becomes the WASM exports. Every module except `ct/` has a `buffers.ts` that defines the static memory layout and the offset getters that all other files in that module import. The `ct/` module is intentionally minimal: a single `index.ts` whose layout is implicit in its single 64 KB page.
159
+
160
+ ```
161
+ src/asm/
162
+ ├── chacha20/
163
+ │ ├── index.ts
164
+ ├── chacha20.ts ← block function (RFC 8439)
165
+ ├── chacha20_simd_4x.ts ← SIMD 4-wide inter-block keystream
166
+ │ ├── poly1305.ts one-time MAC
167
+ │ ├── wipe.ts module-wide buffer zeroizer
168
+ │ └── buffers.ts
169
+ ├── ct/
170
+ └── index.ts v128 XOR-accumulate constant-time compare
171
+ ├── kyber/
172
+ │ ├── index.ts
173
+ │ ├── ntt.ts scalar NTT/invNTT + zetas table
174
+ │ ├── ntt_simd.ts v128 NTT butterflies, fqmul_8x, barrett_reduce_8x
175
+ │ ├── reduce.ts Montgomery/Barrett reduction, fqmul
176
+ │ ├── poly.ts polynomial serialization, compression, basemul
177
+ ├── poly_simd.ts SIMD poly add/sub/reduce/ntt wrappers
178
+ ├── polyvec.ts ← k-wide polyvec operations
179
+ │ ├── cbd.ts centered binomial distribution (η=2, η=3)
180
+ │ ├── sampling.ts uniform rejection sampling
181
+ │ ├── verify.ts constant-time compare and conditional move
182
+ │ ├── params.ts Q, QINV, MONT, Barrett/compression constants
183
+ └── buffers.ts
184
+ ├── serpent/
185
+ │ ├── index.ts
186
+ ├── serpent.ts block function + key schedule
187
+ ├── serpent_unrolled.ts unrolled S-boxes and round functions
188
+ ├── serpent_simd.ts ← SIMD bitsliced block operations
189
+ │ ├── cbc.ts CBC mode
190
+ │ ├── cbc_simd.ts ← SIMD CBC decrypt
191
+ │ ├── ctr.ts ← CTR mode
192
+ │ ├── ctr_simd.ts ← SIMD CTR 4-wide inter-block
193
+ └── buffers.ts
194
+ ├── sha2/
195
+ │ ├── index.ts
196
+ │ ├── sha256.ts
197
+ │ ├── sha512.ts ← shared by SHA-512 and SHA-384
198
+ │ ├── hmac.ts ← HMAC-SHA256
199
+ ├── hmac512.ts ← HMAC-SHA512 and HMAC-SHA384
200
+ └── buffers.ts
201
+ └── sha3/
202
+ ├── index.ts
203
+ ├── keccak.ts Keccak-f[1600] permutation, sponge absorb/squeeze
204
+ └── buffers.ts
205
+ ```
206
+
207
+ ### TypeScript layer
208
+
209
+ `src/ts/` is the public API layer. Each subdirectory is a published npm subpath; top-level files cover cross-cutting concerns and standalone utilities.
210
+
211
+ **Subpath conventions.** Every cipher and hash module has an `index.ts` barrel, a `types.ts` for TypeScript-only declarations, and an `embedded.ts` that re-exports its gzip+base64 WASM blob from `src/ts/embedded/`. The `keccak/` alias subpath omits `types.ts` and re-exports sha3's instead. The `ratchet/` and `stream/` modules have no `embedded.ts` because they compose other modules and ship no WASM of their own.
212
+
213
+ **Cipher modules** (`serpent/`, `chacha20/`) add a `cipher-suite.ts` (the `CipherSuite` implementation for STREAM), a `pool-worker.ts` (Web Worker source for `SealStreamPool`), a `generator.ts` (Fortuna `Generator`), and a `shared-ops.ts` (serpent) or `ops.ts` (chacha20) holding pure primitive functions shared between the cipher-suite and the pool worker.
214
+
215
+ **Hash modules** (`sha2/`, `sha3/`) add a `hash.ts` (the stateless Fortuna `HashFn`).
216
+
217
+ **Build artifacts.** `ct-wasm.ts` and the `embedded/` directory hold auto-generated outputs that only exist after `bun run build`. Both are gitignored. `ct-wasm.ts` is the inline raw byte array of the ct WASM module. `embedded/` holds gzip+base64 blobs of each WASM binary (from `scripts/embed-wasm.ts`) and IIFE source strings for each pool worker (from `scripts/embed-workers.ts`).
218
+
219
+ ```
220
+ src/ts/
221
+ ├── chacha20/
222
+ │ ├── cipher-suite.ts
223
+ │ ├── embedded.ts
224
+ │ ├── generator.ts
225
+ │ ├── index.ts
226
+ │ ├── ops.ts
227
+ │ ├── pool-worker.ts
228
+ │ └── types.ts
229
+ ├── ct-wasm.ts ← gitignored build artifact: raw ct WASM bytes
230
+ ├── embedded/ ← gitignored build artifacts
231
+ │ ├── chacha20-pool-worker.ts ← ChaCha20 pool-worker IIFE source string
232
+ │ ├── chacha20.ts ← chacha20.wasm gzip+base64 blob
233
+ │ ├── kyber.ts ← kyber.wasm gzip+base64 blob
234
+ │ ├── serpent-pool-worker.ts ← Serpent pool-worker IIFE source string
235
+ │ ├── serpent.ts ← serpent.wasm gzip+base64 blob
236
+ │ ├── sha2.ts ← sha2.wasm gzip+base64 blob
237
+ │ └── sha3.ts ← sha3.wasm gzip+base64 blob
238
+ ├── errors.ts ← AuthenticationError
239
+ ├── fortuna.ts ← Fortuna CSPRNG (composes pluggable Generator + HashFn)
240
+ ├── index.ts ← root barrel + dispatching init()
241
+ ├── init.ts ← initModule(), module cache, isInitialized
242
+ ├── keccak/ ← alias subpath; same WASM and instance slot as sha3
243
+ │ ├── embedded.ts
244
+ │ └── index.ts
245
+ ├── kyber/
246
+ │ ├── embedded.ts
247
+ │ ├── indcpa.ts ← IND-CPA encrypt/decrypt + matrix generation
248
+ │ ├── index.ts
249
+ │ ├── kem.ts ← Fujisaki-Okamoto transform (keygen, encaps, decaps)
250
+ │ ├── params.ts ← MLKEM512, MLKEM768, MLKEM1024 parameter sets
251
+ │ ├── suite.ts ← KyberSuite (hybrid KEM+AEAD CipherSuite factory)
252
+ │ ├── types.ts
253
+ │ └── validate.ts ← key validation (FIPS 203 §7.2, §7.3)
254
+ ├── loader.ts ← loadWasm()/compileWasm() WasmSource dispatch
255
+ ├── ratchet/
256
+ │ ├── index.ts
257
+ │ ├── kdf-chain.ts ← KDFChain (per-message KDF chain, DR §5.2)
258
+ │ ├── ratchet-keypair.ts ← RatchetKeypair (single-use ek/dk wrapper)
259
+ │ ├── root-kdf.ts ← ratchetInit, kemRatchetEncap, kemRatchetDecap (DR §7.2)
260
+ │ ├── skipped-key-store.ts ← SkippedKeyStore (MKSKIPPED cache, DR §3.2/§3.5)
261
+ │ └── types.ts
262
+ ├── serpent/
263
+ │ ├── cipher-suite.ts
264
+ │ ├── embedded.ts
265
+ │ ├── generator.ts
266
+ │ ├── index.ts
267
+ │ ├── pool-worker.ts
268
+ │ ├── serpent-cbc.ts ← SerpentCbc (broken out to avoid circular import)
269
+ │ ├── shared-ops.ts
270
+ │ └── types.ts
271
+ ├── sha2/
272
+ │ ├── embedded.ts
273
+ │ ├── hash.ts
274
+ │ ├── hkdf.ts ← HKDF_SHA256, HKDF_SHA512 (pure TS over HMAC)
275
+ │ ├── index.ts
276
+ │ └── types.ts
277
+ ├── sha3/
278
+ │ ├── embedded.ts
279
+ │ ├── hash.ts
280
+ │ ├── index.ts
281
+ │ └── types.ts
282
+ ├── stream/
283
+ │ ├── constants.ts ← HEADER_SIZE, CHUNK_MIN/MAX, TAG_DATA/FINAL, FLAG_FRAMED
284
+ │ ├── header.ts ← wire format header encode/decode, counter nonce
285
+ │ ├── index.ts
286
+ │ ├── open-stream.ts ← OpenStream (cipher-agnostic streaming decryption)
287
+ │ ├── seal-stream-pool.ts ← SealStreamPool (worker-based parallel batch)
288
+ │ ├── seal-stream.ts ← SealStream (cipher-agnostic streaming encryption)
289
+ │ ├── seal.ts ← Seal (static one-shot AEAD)
290
+ │ └── types.ts
291
+ ├── types.ts ← shared interfaces: Hash, KeyedHash, Blockcipher, Streamcipher, AEAD, Generator, HashFn
292
+ ├── utils.ts ← encoding, wipe, randomBytes, constantTimeEqual, CT_MAX_BYTES, hasSIMD
293
+ └── wasm-source.ts ← WasmSource union type
294
+ ```
295
+
296
+ ### Tests
297
+
298
+ `test/` holds three independent categories of files, used by separate workflows.
299
+
300
+ **Unit tests** (`unit/`) are Vitest suites that compile to a JS target for fast local iteration. The directory mirrors `src/ts/` structure with one folder per module, plus a handful of top-level `.test.ts` files for cross-cutting concerns (init, errors, utils, fortuna). CI splits these by domain via `unit-*.yml` for parallel execution.
301
+
302
+ **End-to-end tests** (`e2e/`) are Playwright suites that exercise the actual WASM artifacts across V8, SpiderMonkey, and JavaScriptCore. They run after the full build, including pool-worker bundling.
303
+
304
+ **Test vectors** (`vectors/`) is the immutable known-answer-test corpus. Files are read-only reference data. Some come from authoritative specifications (FIPS, RFCs, ACVP, NIST CAVP); others are self generated as regression vectors by `scripts/gen-*-vectors.ts`. CI validates KAT file integrity against `SHA256SUMS`.
305
+
306
+ See [test-suite.md](./test-suite.md) for full testing methodology, vector corpus inventory with provenance, and gate discipline.
307
+
308
+ ```
309
+ test/
310
+ ├── e2e/ ← Playwright suites against built WASM in V8, SpiderMonkey, JSC
311
+ ├── unit/
312
+ │ ├── chacha20/
313
+ │ ├── ct/
314
+ │ ├── errors.test.ts
315
+ │ ├── fortuna/
316
+ │ ├── fortuna.test.ts
317
+ │ ├── helpers.ts
318
+ │ ├── init/
319
+ │ ├── init.test.ts
320
+ │ ├── kyber/
321
+ │ ├── loader/
322
+ │ ├── ratchet/
323
+ │ ├── serpent/
324
+ │ ├── sha2/
325
+ │ ├── sha3/
326
+ │ ├── stream/
327
+ │ └── utils.test.ts
328
+ └── vectors/ ← KAT corpus; integrity verified against SHA256SUMS
329
+ ```
330
+
331
+ ### Project files
332
+
333
+ The repository root holds project documentation, package metadata, and tool configuration. Build artifacts that only exist after `bun run build` are listed at the end.
334
+
335
+ **Documentation.** `README.md` is the entry point. `SECURITY.md` covers the vulnerability disclosure policy. `AGENTS.md` is the agent contract that governs how AI agents work in the repo. `CHANGELOG` tracks release history and `LICENSE` is MIT. The `docs/` directory holds the full API reference, audits, benchmarks, and architecture notes (this file lives there).
336
+
337
+ **Package metadata.** `package.json` declares the npm manifest, subpath exports, and scripts. `package-lock.json` and `bun.lock` are the lockfiles for npm and bun respectively; both ship checked in so either tool can install reproducibly.
338
+
339
+ **Tool configs.** `asconfig.json` configures AssemblyScript compilation. `eslint.config.ts` is the active linter, run via `bun fix`. `playwright.config.ts` and `vitest.config.ts` configure the e2e and unit test runners. `tsconfig.json` is the base TypeScript config; `tsconfig.test.json` and `tsconfig.e2e.json` extend it for the test targets. `tslint.json` is a TSLint config (older format).
340
+
341
+ **Build artifacts** (gitignored; only exist after `bun run build`). `build/` holds the raw `.wasm` outputs from AssemblyScript compilation. `dist/` is the published npm package contents (compiled JS, declarations, copied WASM, embedded blobs, doc subset).
342
+
343
+ ```
344
+ .
345
+ ├── build/ ← gitignored: .wasm outputs from AS compilation
346
+ ├── dist/ ← gitignored: published npm package contents
347
+ ├── docs/ ← API reference, audits, benchmarks (this file lives here)
348
+ ├── README.md
349
+ ├── SECURITY.md
350
+ ├── AGENTS.md
351
+ ├── CHANGELOG
352
+ ├── LICENSE
196
353
  ├── package.json
197
- ├── asconfig.json ← four AssemblyScript entry points
198
- ├── tsconfig.json
199
- ├── vitest.config.ts
354
+ ├── package-lock.json
355
+ ├── bun.lock
356
+ ├── asconfig.json
357
+ ├── eslint.config.ts
200
358
  ├── playwright.config.ts
201
- ├── AGENTS.md ← ai agent contract
202
- └── SECURITY.md
359
+ ├── tsconfig.json
360
+ ├── tsconfig.e2e.json
361
+ ├── tsconfig.test.json
362
+ ├── tslint.json
363
+ └── vitest.config.ts
203
364
  ```
204
365
 
205
366
  ---
206
367
 
207
368
  ## Architecture: TypeScript over WASM
208
369
 
370
+
209
371
  The TypeScript layer never implements cryptographic algorithms. It manages the boundary between JavaScript and WebAssembly by writing inputs into WASM linear memory, calling exported functions, and reading back outputs. All algorithm logic resides within AssemblyScript.
210
372
 
211
373
  Higher-level classes like `Seal`, `SealStream`, and `SealStreamPool` are pure TypeScript, but they compose WASM-backed primitives (Serpent-CBC, HMAC-SHA256, ChaCha20-Poly1305, and HKDF-SHA256) rather than implementing new cryptographic logic. TypeScript orchestrates, while WASM computes. Pool workers instantiate their own WASM modules and directly call primitives, bypassing the main-thread module cache.
212
374
 
213
375
  ---
214
376
 
215
- ## Five Independent WASM Modules
377
+ ## Six Independent WASM Modules
216
378
 
217
- Each primitive family compiles to its own `.wasm` binary. Modules are fully
218
- independent, separate linear memories, separate buffer layouts, no shared state.
379
+ Each primitive family compiles to its own `.wasm` binary with fully independent linear memory and buffer layouts. No shared state, no cross-module interference. Five of the six modules load through `init()`. The sixth, `ct`, sits outside the public `Module` union and the `init()` gate; it occupies a single 64 KB memory page and lazy-loads on the first call to `constantTimeEqual`. The ct module backs the public `constantTimeEqual` and `CT_MAX_BYTES` exports from the root barrel; neither requires an `init()` call.
219
380
 
220
- | Module | Binary | Primitives |
221
- |--------|--------|------------|
222
- | `serpent` | `serpent.wasm` | Serpent-256 block cipher: ECB, CTR mode, CBC mode |
223
- | `chacha20` | `chacha20.wasm` | ChaCha20, Poly1305, ChaCha20-Poly1305 AEAD, XChaCha20-Poly1305 AEAD |
224
- | `sha2` | `sha2.wasm` | SHA-256, SHA-384, SHA-512, HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 |
225
- | `sha3` | `sha3.wasm` | SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256 |
226
- | `kyber` | `kyber.wasm` | ML-KEM polynomial arithmetic: SIMD NTT/invNTT (v128 butterflies with scalar tail), basemul, Montgomery/Barrett, CBD, compress, CT verify/cmov |
381
+ |Module|Binary|Primitives|
382
+ |---|---|---|
383
+ |`serpent`|`serpent.wasm`|Serpent-256 block cipher: ECB, CTR mode, CBC mode|
384
+ |`chacha20`|`chacha20.wasm`|ChaCha20, Poly1305, ChaCha20-Poly1305 AEAD, XChaCha20-Poly1305 AEAD|
385
+ |`sha2`|`sha2.wasm`|SHA-256, SHA-384, SHA-512, HMAC-SHA256, HMAC-SHA384, HMAC-SHA512|
386
+ |`sha3`|`sha3.wasm`|SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256|
387
+ |`kyber`|`kyber.wasm`|ML-KEM polynomial arithmetic: SIMD NTT/invNTT (v128 butterflies with scalar tail), basemul, Montgomery/Barrett, CBD, compress, CT verify/cmov|
388
+ |`ct`|`ct.wasm`|SIMD constant-time byte comparison. Backs `constantTimeEqual` and `CT_MAX_BYTES`, lazy-loaded outside `init()`. Single 64 KB page.|
227
389
 
228
390
  **Size.** Consumers who only use Serpent don't load the SHA-3 binary.
229
391
 
230
392
  **Isolation.** Key material in `serpent.wasm` memory cannot bleed into `sha3.wasm` memory even in theory.
231
393
 
232
- Each module's buffer layout starts at offset 0 and is defined in its own
233
- `buffers.ts`. Buffer layouts are fully independent across modules.
394
+ Each module's buffer layout starts at offset 0 and is defined in its own `buffers.ts`. Buffer layouts are fully independent across modules.
234
395
 
235
396
  ### Module contents
236
397
 
237
- **`serpent.wasm`**
238
- Serpent-256 block cipher. Key schedule, block encrypt, block decrypt. CTR mode
239
- chunked streaming encrypt/decrypt. CBC mode chunked encrypt/decrypt. SIMD
240
- variants for CTR 4-wide inter-block and CBC decrypt parallelism.
241
- Source: `src/asm/serpent/`
242
-
243
- The serpent TypeScript module includes `SerpentCipher` (CipherSuite implementation
244
- for the STREAM construction: Serpent-CBC + HMAC-SHA256 with HKDF key derivation).
245
- Requires `serpent` and `sha2` to be initialized.
246
-
247
- **`chacha20.wasm`**
248
- ChaCha20 stream cipher (RFC 8439). Poly1305 MAC (RFC 8439 §2.5). ChaCha20-Poly1305
249
- AEAD (RFC 8439 §2.8). XChaCha20-Poly1305 AEAD (draft-irtf-cfrg-xchacha).
250
- HChaCha20 subkey derivation. SIMD 4-wide inter-block parallelism.
251
- Source: `src/asm/chacha20/`
252
-
253
- The chacha20 TypeScript module includes `XChaCha20Cipher` (CipherSuite implementation
254
- for the STREAM construction: XChaCha20-Poly1305 with HKDF key derivation).
255
- Pool workers are internal, loaded by `SealStreamPool` at runtime, not registered in the package exports map.
256
-
257
- **`sha2.wasm`**
258
- SHA-256 and SHA-512 (FIPS 180-4). SHA-384 (SHA-512 with different IVs, truncated
259
- output, shares all SHA-512 buffers and compress function). HMAC-SHA256,
260
- HMAC-SHA512, HMAC-SHA384 (RFC 2104). HKDF-SHA256 and HKDF-SHA512 (RFC 5869)
261
- are pure TypeScript compositions over HMAC, with no new WASM logic.
262
- Source: `src/asm/sha2/`
263
-
264
- **`sha3.wasm`**
265
- Keccak-f[1600] permutation (FIPS 202). SHA3-224, SHA3-256, SHA3-384, SHA3-512.
266
- SHAKE128, SHAKE256 (XOFs, multi-squeeze capable, unbounded output length).
267
- All six variants share one permutation, differing only in rate, domain
268
- separation byte, and output length.
269
- Source: `src/asm/sha3/`
270
-
271
- **`kyber.wasm`**
272
- ML-KEM (FIPS 203) polynomial arithmetic. Montgomery and Barrett reduction,
273
- 7-layer NTT and inverse NTT, basemul in Z_q[X]/(X²-ζ), centered binomial
274
- distribution sampling (η=2, η=3), division-free compression/decompression
275
- (all 5 bit-width paths: 4, 5, 10, 11, 1-bit), rejection sampling for matrix
276
- generation, constant-time byte comparison and conditional move. Requires
277
- WebAssembly SIMD (`v128` instructions) for NTT and polynomial arithmetic.
278
- 3 pages (192 KB) linear memory with 10 poly slots, 8 polyvec slots, and
279
- dedicated byte buffers for keys/ciphertexts.
280
- Source: `src/asm/kyber/`
398
+ **`serpent.wasm`** implements Serpent-256, a 128-bit block cipher. It handles key scheduling, block encryption and decryption, and both CTR and CBC streaming modes with SIMD variants for inter-block parallelism. See: [Serpent-256 WASM Module Reference](./asm_serpent.md)
281
399
 
282
- The kyber TypeScript module includes `MlKem512`, `MlKem768`, `MlKem1024`
283
- (KEM classes implementing the Fujisaki-Okamoto transform). All require both
284
- `kyber` and `sha3` to be initialized; the sha3 module provides the Keccak sponge (SHAKE128 for matrix gen, SHAKE256 for noise/J function, SHA3-256 for H, SHA3-512 for G).
400
+ [The TypeScript module](./serpent.md) wraps this with `SerpentCipher`, a CipherSuite that combines Serpent-CBC with HMAC-SHA256 and HKDF key derivation for the STREAM construction. Primitive operations (HMAC, CBC, PKCS7 padding) live in `serpent/shared-ops.ts` and are reused by both the main thread and pool workers, guaranteeing byte-identical output and consistent Vaudenay 2002 padding normalization. Requires `serpent` and `sha2` to be initialized.
401
+
402
+ **`chacha20.wasm`** implements the full ChaCha20-Poly1305 AEAD family per RFC 8439 and draft-irtf-cfrg-xchacha. It includes ChaCha20 stream cipher, Poly1305 one-time MAC, the AEAD construction, HChaCha20 for nonce extension, and SIMD 4-wide inter-block parallelism. See: [ChaCha20/Poly1305 WASM Reference](./asm_chacha.md)
403
+
404
+ [The TypeScript module](./chacha20.md) exports `XChaCha20Cipher`, a CipherSuite implementation for STREAM using XChaCha20-Poly1305 with HKDF key derivation. Pool workers load internally via `SealStreamPool` at runtime and don't appear in the package exports map.
405
+
406
+ **`sha2.wasm`** implements SHA-256 and SHA-512 per FIPS 180-4, plus SHA-384 (which reuses SHA-512's buffer and compress function with different IVs and truncation). It also provides HMAC per RFC 2104 for all three variants. HKDF-SHA256 and HKDF-SHA512 (RFC 5869) are pure TypeScript compositions over HMAC with no new WASM logic. See: [SHA-2 WASM Reference](./asm_sha2.md)
407
+
408
+ **`sha3.wasm`** implements the Keccak-f[1600] permutation per FIPS 202. All SHA3 variants (SHA3-224, SHA3-256, SHA3-384, SHA3-512) and XOF variants (SHAKE128, SHAKE256) share a single permutation, differing only in rate, domain separation byte, and output length. SHAKE supports unbounded multi-squeeze output. See: [SHA-3 WASM Reference](./asm_sha3.md)
409
+
410
+ **`kyber.wasm`** implements ML-KEM polynomial arithmetic per FIPS 203. It includes Montgomery and Barrett reduction, 7-layer NTT and inverse NTT with SIMD butterflies, basemul in Z_q[X]/(X²-ζ), centered binomial distribution sampling (η=2 and η=3), compression and decompression across all five bit-width paths, rejection sampling for matrix generation, and constant-time byte comparison and conditional move. Requires WebAssembly SIMD (`v128` instructions). Uses 3 memory pages (192 KB) with 10 polynomial slots, 8 polynomial vector slots, and dedicated buffers for keys and ciphertexts. See: [Kyber WASM Reference](./asm_kyber.md)
411
+
412
+ [The TypeScript module](./kyber.md) exports `MlKem512`, `MlKem768`, and `MlKem1024`—KEM classes implementing the Fujisaki-Okamoto transform. All three require both `kyber` and `sha3` to be initialized; the sha3 module provides the Keccak sponge for matrix generation (SHAKE128), noise sampling (SHAKE256), and finalization (SHA3-256 for H, SHA3-512 for G).
413
+
414
+ **`ct.wasm`** implements constant-time byte array equality with a single SIMD-only primitive. The module exports `compare(aOff, bOff, len)`, which reads both arrays directly from caller-specified offsets in linear memory and returns 1 if all bytes match, 0 otherwise. Comparison is zero-copy: no internal staging buffers, no buffer slots, no `wipeBuffers` export. The implementation is structurally branch-free. A `v128.xor`/`v128.or` accumulator processes 16-byte blocks, a scalar tail handles any remainder, and the final zero-test is an arithmetic shift, not a conditional. Requires WebAssembly SIMD (`v128` instructions); if the runtime lacks SIMD or compilation fails, the first call throws a branded error. See: [Constant-Time WASM Reference](asm_ct.md)
415
+
416
+ [The TypeScript module](./utils.md#constanttimeequal) exports `constantTimeEqual` and `CT_MAX_BYTES` from the root barrel. The wrapper instantiates the WASM synchronously on first call and caches it for subsequent calls. It writes both arrays into linear memory, calls `compare`, and zeroes both regions in a `finally` block before returning. `CT_MAX_BYTES` is 32 KB per side; the 64 KB page holds two equal-length inputs.
285
417
 
286
418
  ---
287
419
 
288
420
  ## `init()` API
289
421
 
290
- WASM instantiation is async. `init()` is the initialization gate, call it once before using any cryptographic class. The cost is explicit and the developer controls when it is paid.
422
+ WASM instantiation is async. [`init()`](./init.md) is the initialization gate, call it once before using any cryptographic class. The cost is explicit and the developer controls when it is paid.
291
423
 
292
424
  ### Signature
293
425
 
@@ -308,19 +440,15 @@ async function init(
308
440
  ): Promise<void>
309
441
  ```
310
442
 
311
- The loading strategy is inferred from the source type, so there is no need
312
- for a mode string. Each module also exports its own init function, such as
313
- `serpentInit(source)`, `chacha20Init(source)`, `sha2Init(source)`,
314
- `sha3Init(source)`, `keccakInit(source)`, and `kyberInit(source)`,
315
- enabling tree-shakeable imports.
443
+ The loading strategy is inferred from the source type, so there is no need for a mode string. Each module also exports its own init function, such as `serpentInit(source)`, `chacha20Init(source)`, `sha2Init(source)`, `sha3Init(source)`, `keccakInit(source)`, and `kyberInit(source)`, enabling tree-shakeable imports.
316
444
 
317
445
  > [!NOTE]
318
- > **`'keccak'` is an alias for `'sha3'`.** Both names are accepted by `init()`, `initModule()`, `getInstance()`, and `isInitialized()`. They share the same WASM binary and the same instance slot. The alias exists so Kyber/ML-KEM consumers can write `init({ keccak: keccakWasm })` using the semantically correct name for the underlying sponge primitive.
446
+ > **`keccak` is an alias for `sha3`.** Both names are accepted by `init()`, `initModule()`, `getInstance()`, and `isInitialized()`. They share the same WASM binary and the same instance slot. The alias exists so Kyber/ML-KEM consumers can write `init({ keccak: keccakWasm })` using the semantically correct name for the underlying sponge primitive.
319
447
 
320
448
  ### Embedded subpath exports
321
449
 
322
- Each module provides a `/embedded` subpath that exports the gzip+base64
323
- blob as a ready-to-use `WasmSource`:
450
+ Each module provides a `/embedded` subpath that exports the gzip+base64 blob as a ready-to-use `WasmSource`:
451
+
324
452
  ```typescript
325
453
  import { init } from 'leviathan-crypto'
326
454
  import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
@@ -331,55 +459,50 @@ await init({ serpent: serpentWasm, sha2: sha2Wasm })
331
459
 
332
460
  ### Behavioral contracts
333
461
 
334
- **Idempotent initialization.** Calling `init()` on an already initialized
335
- module is a no-op. It is safe to call `init()` from multiple locations
336
- within the codebase.
462
+ **Idempotent initialization.** Calling `init()` on an already initialized module is a no-op. It is safe to call `init()` from multiple locations within the codebase.
337
463
 
338
- **Module-scope cache.** Each `WebAssembly.Instance` is cached at module
339
- scope after initial instantiation. All subsequent class constructions use
340
- the cached instance with no recompilation.
464
+ **Module-scope cache.** Each `WebAssembly.Instance` is cached at module scope after initial instantiation. All subsequent class constructions use the cached instance with no recompilation.
341
465
 
342
- **Error before initialization.** Invoking any cryptographic class before
343
- calling `init()` throws a clear error prompting the developer to call
344
- `init({ <module>: ... })` first.
466
+ **Error before initialization.** Invoking any cryptographic class before calling `init()` throws a clear error prompting the developer to call `init({ <module>: ... })` first.
345
467
 
346
- **No implicit initialization.** Classes never call `init()` automatically
347
- on first use. Explicit initialization is preferable to hidden costs.
468
+ **No implicit initialization.** Classes never call `init()` automatically on first use. Explicit initialization is preferable to hidden costs.
348
469
 
349
- **Thread safety.** The main thread uses a single WASM instance per module.
350
- `SealStreamPool` provides worker-based parallelism. Each pool worker owns
351
- its own WASM instances with isolated linear memory, bypassing the main-thread
352
- cache entirely. For other primitives, create one instance per Worker if
353
- Workers are used.
470
+ **Thread safety.** The main thread uses a single WASM instance per module. `SealStreamPool` provides worker-based parallelism. Each pool worker is spawned from an IIFE bundled at build time and instantiates its own WASM modules with isolated linear memory, bypassing the main-thread cache entirely. For other primitives, create one instance per Worker if Workers are used.
354
471
 
355
472
  ---
356
473
 
357
474
  ## Public API Classes
358
475
 
359
- Names match conventional cryptographic notation.
360
-
361
- | Module | Classes |
362
- |--------|---------|
363
- | `serpent` + `sha2` | `SerpentCipher` |
364
- | `serpent` | `Serpent`, `SerpentCtr`, `SerpentCbc` |
365
- | `chacha20` | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, `XChaCha20Cipher` |
366
- | `sha2` | `SHA256`, `SHA384`, `SHA512`, `HMAC_SHA256`, `HMAC_SHA384`, `HMAC_SHA512`, `HKDF_SHA256`, `HKDF_SHA512` |
367
- | `sha3` | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256` |
368
- | `kyber` + `sha3` | `MlKem512`, `MlKem768`, `MlKem1024` |
369
- | `kyber` + `sha3` + inner cipher | `KyberSuite` (hybrid KEM+AEAD factory) |
370
- | `stream` | `Seal`, `SealStream`, `OpenStream`, `SealStreamPool` |
371
- | `serpent` + `sha2` | `Fortuna` |
372
-
373
- HMAC names use underscore separator (`HMAC_SHA256`) matching RFC convention.
374
- SHA-3 names use underscore separator (`SHA3_256`) for readability.
375
- `SealStream`, `OpenStream`, and `SealStreamPool` are cipher-agnostic; you select the cipher by passing `XChaCha20Cipher` or `SerpentCipher` at construction.
376
-
377
- **`Fortuna`** requires `await Fortuna.create()` rather than `new Fortuna()` due
378
- to the async `init()` dependency on two modules.
476
+ | Module | Classes |
477
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------- |
478
+ | `serpent` + `sha2` | `SerpentCipher` |
479
+ | `serpent` | `Serpent`, `SerpentCtr`, `SerpentCbc` |
480
+ | `chacha20` | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, `XChaCha20Cipher` |
481
+ | `sha2` | `SHA256`, `SHA384`, `SHA512`, `HMAC_SHA256`, `HMAC_SHA384`, `HMAC_SHA512`, `HKDF_SHA256`, `HKDF_SHA512` |
482
+ | `sha3` | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256` |
483
+ | `kyber` + `sha3` | `MlKem512`, `MlKem768`, `MlKem1024` |
484
+ | `kyber` + `sha3` + inner cipher | `KyberSuite` (hybrid KEM+AEAD factory) |
485
+ | `sha2` | `ratchetInit`, `KDFChain`, `SkippedKeyStore` |
486
+ | `kyber` + `sha3` + `sha2` | `kemRatchetEncap`, `kemRatchetDecap`, `RatchetKeypair` |
487
+ | `stream` | `Seal`, `SealStream`, `OpenStream`, `SealStreamPool` |
488
+ | `serpent` + `sha2` | `Fortuna` with `SerpentGenerator` + `SHA256Hash` |
489
+ | `serpent` + `sha3` | `Fortuna` with `SerpentGenerator` + `SHA3_256Hash` |
490
+ | `chacha20` + `sha2` | `Fortuna` with `ChaCha20Generator` + `SHA256Hash` |
491
+ | `chacha20` + `sha3` | `Fortuna` with `ChaCha20Generator` + `SHA3_256Hash` |
492
+
493
+ >[!NOTE]
494
+ > Class Names match conventional cryptographic notation.
495
+
496
+ - HMAC names use underscore separator (`HMAC_SHA256`) matching RFC convention.
497
+ - SHA-3 names use underscore separator (`SHA3_256`) for readability.
498
+ - Ratchet exports are KDF primitives from Signal's Sparse Post-Quantum Ratchet spec; session state, message ordering, and header format remain application concerns.
499
+ - **`Fortuna`** requires `await Fortuna.create({ generator, hash })` rather than `new Fortuna()`. Required modules depend on the generator and hash you pass. See [fortuna.md](./fortuna.md) for valid combinations.
500
+ - `SealStream`, `OpenStream`, and `SealStreamPool` are cipher-agnostic; you select the cipher by passing `XChaCha20Cipher` or `SerpentCipher` at construction.
379
501
 
380
502
  ### Usage pattern
381
503
 
382
504
  All WASM-backed classes follow the same pattern:
505
+
383
506
  ```typescript
384
507
  import { init, Seal, SerpentCipher, SHA3_256 } from 'leviathan-crypto'
385
508
  import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
@@ -399,35 +522,38 @@ const digest = hasher.hash(message)
399
522
 
400
523
  Pure TypeScript utilities ship alongside the WASM-backed primitives:
401
524
 
402
- | Category | Exports |
403
- |----------|---------|
404
- | Encoding | `hexToBytes`, `bytesToHex`, `utf8ToBytes`, `bytesToUtf8`, `base64ToBytes`, `bytesToBase64` |
405
- | Security | `constantTimeEqual`, `wipe`, `xor` |
406
- | Helpers | `concat`, `randomBytes` |
407
- | Types | `Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD` |
525
+ |Category|Exports|
526
+ |---|---|
527
+ |Encoding|`hexToBytes`, `bytesToHex`, `utf8ToBytes`, `bytesToUtf8`, `base64ToBytes`, `bytesToBase64`|
528
+ |Security|`constantTimeEqual`, `CT_MAX_BYTES`, `wipe`, `xor`|
529
+ |Helpers|`concat`, `randomBytes`, `hasSIMD`|
530
+ |Types|`Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD`|
408
531
 
409
532
  ---
410
533
 
411
534
  ## Build Pipeline
412
535
 
536
+
413
537
  1. `npm run build:asm`: AssemblyScript compiler reads `src/asm/*/index.ts`, emits `build/*.wasm`
414
538
  2. `npm run build:embed`: `scripts/embed-wasm.ts` reads each `.wasm`, gzip compresses, base64 encodes, writes to `src/ts/embedded/*.ts` and per-module `src/ts/*/embedded.ts`
415
- 3. `npm run build:ts`: TypeScript compiler emits `dist/`
416
- 4. `cp build/*.wasm dist/`: WASM binaries copied for URL-based consumers
417
- 5. At runtime (subpath): `serpentInit(serpentWasm)` `initModule()` `loadWasm(source)` decode gzip+base64 → `WebAssembly.instantiate` → cache in `init.ts`
418
- 6. At runtime (root): `init({ serpent: serpentWasm, sha2: sha2Wasm })` → dispatches to each module's init function via `Promise.all` → same path as step 5 per module
539
+ 3. `npm run build:embed-workers`: `scripts/embed-workers.ts` bundles each pool worker into a self-contained IIFE via esbuild and writes the source to `src/ts/embedded/<cipher>-pool-worker.ts` as a string export
540
+ 4. `npm run build:ts`: TypeScript compiler emits `dist/`
541
+ 5. `cp build/*.wasm dist/`: WASM binaries copied for URL-based consumers
542
+ 6. At runtime (subpath): `serpentInit(serpentWasm)` `initModule()` → `loadWasm(source)` decode gzip+base64 `WebAssembly.instantiate` → cache in `init.ts`
543
+ 7. At runtime (root): `init({ serpent: serpentWasm, sha2: sha2Wasm })` → dispatches to each module's init function via `Promise.all` → same path as step 6 per module
419
544
 
420
- `src/ts/embedded/` is gitignored; these files are build artifacts derived from the WASM binaries. There is one source of truth for each binary: the AssemblyScript source.
545
+ `src/ts/embedded/` is gitignored; these files are build artifacts. The WASM blobs (`<module>.ts`) derive from the AssemblyScript source in `src/asm/`. The pool-worker bundles (`<cipher>-pool-worker.ts`) derive from the worker source in `src/ts/<cipher>/pool-worker.ts`, bundled as a self-contained IIFE by `scripts/embed-workers.ts`.
421
546
 
422
547
  ---
423
548
 
424
- ## Module Relationship Diagrams
549
+ ## Module Relationships
425
550
 
426
551
  ### ASM layer: internal import graph
427
552
 
428
553
  Each WASM module is fully independent. No cross-module imports exist.
429
554
 
430
555
  **Serpent (`src/asm/serpent/`)**
556
+
431
557
  ```
432
558
  buffers.ts
433
559
  <- serpent.ts (offsets for key, block, subkey, work, CBC IV)
@@ -454,6 +580,7 @@ index.ts
454
580
  ```
455
581
 
456
582
  **ChaCha (`src/asm/chacha20/`)**
583
+
457
584
  ```
458
585
  buffers.ts
459
586
  <- chacha20.ts (key, nonce, counter, block, state, poly key, xchacha offsets)
@@ -539,158 +666,128 @@ index.ts
539
666
 
540
667
  ### TS layer: internal import graph
541
668
 
542
- ```
543
- +---------------------+
544
- | index.ts | <- barrel: dispatching init()
545
- | (public API root) | + re-exports everything
546
- +--+--+--+--+--+--+--+
547
- | | | | | |
548
- +-------------------------+ | | | | +----------------------+
549
- | +----------------+ | | +----------+ |
550
- v v v v v v
551
- serpent/ chacha20/ sha2/ sha3/ fortuna.ts stream/
552
- index.ts index.ts index.ts index.ts index.ts
553
- | | | | | | |
554
- | | +-> ops.ts | | +-> init.ts |
555
- | | | | +-> serpent/ |
556
- | +-> cipher- | | +-> sha2/ +-> seal-stream.ts
557
- | suite.ts | | +-> utils.ts +-> open-stream.ts
558
- +-> cipher- | | | +-> seal-stream-pool.ts
559
- suite.ts +-> pool- +-> hkdf.ts +-> header.ts
560
- | worker.ts +-> constants.ts
561
- +-> pool- | +-> types.ts
562
- worker.ts |
563
- |
564
- All module index.ts files ──────────────────────────> init.ts <── getInstance()
565
- initModule()
566
- All */embedded.ts files ──────────────────────────> embedded/*.ts (gzip+base64 blobs)
567
- ```
568
669
 
569
- Each module's init function (`serpentInit`, `chacha20Init`, `sha2Init`,
570
- `sha3Init`, `kyberInit`) calls `initModule()` from `init.ts`, passing a `WasmSource`.
571
- `initModule()` delegates to `loadWasm(source)` in `loader.ts`. The loader
572
- infers the loading strategy from the source type, with no mode string and no knowledge of module names or embedded file paths.
670
+ Each module's init function (`serpentInit`, `chacha20Init`, `sha2Init`, `sha3Init`, `kyberInit`) calls `initModule()` from `init.ts`, passing a `WasmSource`. `initModule()` delegates to `loadWasm(source)` in `loader.ts`. The loader infers the loading strategy from the source type, with no mode string and no knowledge of module names or embedded file paths.
573
671
 
574
- Pool workers (`serpent/pool-worker.ts`, `chacha20/pool-worker.ts`) instantiate
575
- their own WASM modules from pre-compiled `WebAssembly.Module` objects passed
576
- via `postMessage`. They do not use `initModule()` or the main-thread cache.
672
+ Pool workers (`serpent/pool-worker.ts`, `chacha20/pool-worker.ts`) instantiate their own WASM modules from pre-compiled `WebAssembly.Module` objects passed via `postMessage`. They do not use `initModule()` or the main-thread cache. Workers are spawned from blob URLs constructed in `cipher-suite.ts` over an IIFE source string built at lib build time (`src/ts/embedded/<cipher>-pool-worker.ts`). The `pool-worker.ts` file itself is the source the bundler reads, not the runtime spawn entry.
577
673
 
578
674
  ---
579
675
 
580
676
  ### TS-to-WASM mapping
581
677
 
582
- Each TS wrapper class maps to one WASM module and specific exported functions.
583
- Tier 2 composition classes are pure TypeScript; they call Tier 1 classes rather than WASM functions directly.
678
+ Each TS wrapper class maps to one WASM module and specific exported functions. Tier 2 composition classes are pure TypeScript; they call Tier 1 classes rather than WASM functions directly.
584
679
 
585
680
  **serpent/index.ts → asm/serpent/ (Tier 1: direct WASM callers)**
586
681
 
587
- | TS Class | WASM functions called |
588
- |----------|---------------------|
589
- | `Serpent` | `loadKey`, `encryptBlock`, `decryptBlock`, `wipeBuffers` + buffer getters |
590
- | `SerpentCtr` | `loadKey`, `resetCounter`, `setCounter`, `encryptChunk`, `encryptChunk_simd`, `wipeBuffers` + buffer getters |
591
- | `SerpentCbc` | `loadKey`, `cbcEncryptChunk`, `cbcDecryptChunk`, `cbcDecryptChunk_simd`, `wipeBuffers` + buffer getters |
682
+ |TS Class|WASM functions called|
683
+ |---|---|
684
+ |`Serpent`|`loadKey`, `encryptBlock`, `decryptBlock`, `wipeBuffers` + buffer getters|
685
+ |`SerpentCtr`|`loadKey`, `resetCounter`, `setCounter`, `encryptChunk`, `encryptChunk_simd`, `wipeBuffers` + buffer getters|
686
+ |`SerpentCbc`|`loadKey`, `cbcEncryptChunk`, `cbcDecryptChunk`, `cbcDecryptChunk_simd`, `wipeBuffers` + buffer getters|
687
+ |`SerpentGenerator`|`loadKey`, `encryptBlock`, `wipeBuffers` + buffer getters|
592
688
 
593
689
  **chacha20/index.ts → asm/chacha20/ (Tier 1: direct WASM callers)**
594
690
 
595
- | TS Class | WASM functions called |
596
- |----------|---------------------|
597
- | `ChaCha20` | `chachaLoadKey`, `chachaSetCounter`, `chachaEncryptChunk`, `chachaEncryptChunk_simd`, `wipeBuffers` + buffer getters |
598
- | `Poly1305` | `polyInit`, `polyUpdate`, `polyFinal`, `wipeBuffers` + buffer getters |
599
- | `ChaCha20Poly1305` | `chachaLoadKey`, `chachaSetCounter`, `chachaGenPolyKey`, `chachaEncryptChunk`, `polyInit`, `polyUpdate`, `polyFinal`, `wipeBuffers` + buffer getters (via `ops.ts`) |
600
- | `XChaCha20Poly1305` | All of `ChaCha20Poly1305` + `hchacha20` + xchacha buffer getters (via `ops.ts`) |
691
+ |TS Class|WASM functions called|
692
+ |---|---|
693
+ |`ChaCha20`|`chachaLoadKey`, `chachaSetCounter`, `chachaEncryptChunk`, `chachaEncryptChunk_simd`, `wipeBuffers` + buffer getters|
694
+ |`Poly1305`|`polyInit`, `polyUpdate`, `polyFinal`, `wipeBuffers` + buffer getters|
695
+ |`ChaCha20Poly1305`|`chachaLoadKey`, `chachaSetCounter`, `chachaGenPolyKey`, `chachaEncryptChunk`, `polyInit`, `polyUpdate`, `polyFinal`, `wipeBuffers` + buffer getters (via `ops.ts`)|
696
+ |`XChaCha20Poly1305`|All of `ChaCha20Poly1305` + `hchacha20` + xchacha buffer getters (via `ops.ts`)|
697
+ |`ChaCha20Generator`|`chachaLoadKey`, `chachaSetCounter`, `chachaEncryptChunk_simd`, `wipeBuffers` + buffer getters|
601
698
 
602
699
  **sha2/index.ts → asm/sha2/ (Tier 1: direct WASM callers)**
603
700
 
604
- | TS Class | WASM functions called |
605
- |----------|---------------------|
606
- | `SHA256` | `sha256Init`, `sha256Update`, `sha256Final`, `wipeBuffers` + buffer getters |
607
- | `SHA512` | `sha512Init`, `sha512Update`, `sha512Final`, `wipeBuffers` + buffer getters |
608
- | `SHA384` | `sha384Init`, `sha512Update`, `sha384Final`, `wipeBuffers` + buffer getters |
609
- | `HMAC_SHA256` | `hmac256Init`, `hmac256Update`, `hmac256Final`, `sha256Init`, `sha256Update`, `sha256Final`, `wipeBuffers` + buffer getters |
610
- | `HMAC_SHA512` | `hmac512Init`, `hmac512Update`, `hmac512Final`, `sha512Init`, `sha512Update`, `sha512Final`, `wipeBuffers` + buffer getters |
611
- | `HMAC_SHA384` | `hmac384Init`, `hmac384Update`, `hmac384Final`, `sha384Init`, `sha512Update`, `sha384Final`, `wipeBuffers` + buffer getters |
701
+ |TS Class|WASM functions called|
702
+ |---|---|
703
+ |`SHA256`|`sha256Init`, `sha256Update`, `sha256Final`, `wipeBuffers` + buffer getters|
704
+ |`SHA512`|`sha512Init`, `sha512Update`, `sha512Final`, `wipeBuffers` + buffer getters|
705
+ |`SHA384`|`sha384Init`, `sha512Update`, `sha384Final`, `wipeBuffers` + buffer getters|
706
+ |`HMAC_SHA256`|`hmac256Init`, `hmac256Update`, `hmac256Final`, `sha256Init`, `sha256Update`, `sha256Final`, `wipeBuffers` + buffer getters|
707
+ |`HMAC_SHA512`|`hmac512Init`, `hmac512Update`, `hmac512Final`, `sha512Init`, `sha512Update`, `sha512Final`, `wipeBuffers` + buffer getters|
708
+ |`HMAC_SHA384`|`hmac384Init`, `hmac384Update`, `hmac384Final`, `sha384Init`, `sha512Update`, `sha384Final`, `wipeBuffers` + buffer getters|
709
+ |`SHA256Hash`|`sha256Init`, `sha256Update`, `sha256Final`, `wipeBuffers` + buffer getters|
612
710
 
613
711
  **sha3/index.ts → asm/sha3/ (Tier 1: direct WASM callers)**
614
712
 
615
- | TS Class | WASM functions called |
616
- |----------|---------------------|
617
- | `SHA3_224` | `sha3_224Init`, `keccakAbsorb`, `sha3_224Final`, `wipeBuffers` + buffer getters |
618
- | `SHA3_256` | `sha3_256Init`, `keccakAbsorb`, `sha3_256Final`, `wipeBuffers` + buffer getters |
619
- | `SHA3_384` | `sha3_384Init`, `keccakAbsorb`, `sha3_384Final`, `wipeBuffers` + buffer getters |
620
- | `SHA3_512` | `sha3_512Init`, `keccakAbsorb`, `sha3_512Final`, `wipeBuffers` + buffer getters |
621
- | `SHAKE128` | `shake128Init`, `keccakAbsorb`, `shakePad`, `shakeSqueezeBlock`, `wipeBuffers` + buffer getters |
622
- | `SHAKE256` | `shake256Init`, `keccakAbsorb`, `shakePad`, `shakeSqueezeBlock`, `wipeBuffers` + buffer getters |
713
+ |TS Class|WASM functions called|
714
+ |---|---|
715
+ |`SHA3_224`|`sha3_224Init`, `keccakAbsorb`, `sha3_224Final`, `wipeBuffers` + buffer getters|
716
+ |`SHA3_256`|`sha3_256Init`, `keccakAbsorb`, `sha3_256Final`, `wipeBuffers` + buffer getters|
717
+ |`SHA3_384`|`sha3_384Init`, `keccakAbsorb`, `sha3_384Final`, `wipeBuffers` + buffer getters|
718
+ |`SHA3_512`|`sha3_512Init`, `keccakAbsorb`, `sha3_512Final`, `wipeBuffers` + buffer getters|
719
+ |`SHAKE128`|`shake128Init`, `keccakAbsorb`, `shakePad`, `shakeSqueezeBlock`, `wipeBuffers` + buffer getters|
720
+ |`SHAKE256`|`shake256Init`, `keccakAbsorb`, `shakePad`, `shakeSqueezeBlock`, `wipeBuffers` + buffer getters|
721
+ |`SHA3_256Hash`|`sha3_256Init`, `keccakAbsorb`, `sha3_256Final`, `wipeBuffers` + buffer getters|
623
722
 
624
723
  **kyber/index.ts + kyber/kem.ts + kyber/indcpa.ts → asm/kyber/ (Tier 1)**
625
724
 
626
- | TS Class | WASM functions called |
627
- |----------|---------------------|
628
- | `MlKem512`, `MlKem768`, `MlKem1024` | `polyvec_ntt`, `polyvec_invntt`, `polyvec_basemul_acc_montgomery`, `polyvec_add`, `polyvec_reduce`, `polyvec_tobytes`, `polyvec_frombytes`, `polyvec_compress`, `polyvec_decompress`, `poly_ntt`, `poly_invntt`, `poly_tomont`, `poly_add`, `poly_sub`, `poly_reduce`, `poly_basemul_montgomery`, `poly_frommsg`, `poly_tomsg`, `poly_compress`, `poly_decompress`, `poly_getnoise`, `rej_uniform`, `ct_verify`, `ct_cmov`, `wipeBuffers` + buffer getters |
725
+ |TS Class|WASM functions called|
726
+ |---|---|
727
+ |`MlKem512`, `MlKem768`, `MlKem1024`|`polyvec_ntt`, `polyvec_invntt`, `polyvec_basemul_acc_montgomery`, `polyvec_add`, `polyvec_reduce`, `polyvec_tobytes`, `polyvec_frombytes`, `polyvec_compress`, `polyvec_decompress`, `poly_ntt`, `poly_invntt`, `poly_tomont`, `poly_add`, `poly_sub`, `poly_reduce`, `poly_basemul_montgomery`, `poly_frommsg`, `poly_tomsg`, `poly_compress`, `poly_decompress`, `poly_getnoise`, `rej_uniform`, `ct_verify`, `ct_cmov`, `wipeBuffers` + buffer getters|
629
728
 
630
729
  All MlKem classes also call sha3 WASM via `indcpa.ts`: `sha3_256Init`, `sha3_512Init`, `shake128Init`, `shake256Init`, `keccakAbsorb`, `sha3_256Final`, `sha3_512Final`, `shakeFinal`, `shakePad`, `shakeSqueezeBlock`.
631
730
 
632
731
  **Tier 2: pure TS composition**
633
732
 
634
- | TS Class / Object | Composes |
635
- |--------------------|----------|
636
- | `SerpentCipher` | `SerpentCbc` + `HMAC_SHA256` + `HKDF_SHA256` |
637
- | `XChaCha20Cipher` | `ChaCha20Poly1305` (via `ops.ts`) + `HKDF_SHA256` |
638
- | `Seal` | `SealStream` + `OpenStream` (degenerate single-chunk case) |
639
- | `SealStream` | `CipherSuite` (generic — caller provides cipher) |
640
- | `OpenStream` | `CipherSuite` (generic — caller provides cipher) |
641
- | `SealStreamPool` | `CipherSuite` + `compileWasm()` + Web Workers |
642
- | `HKDF_SHA256` | `HMAC_SHA256` (extract + expand per RFC 5869) |
643
- | `HKDF_SHA512` | `HMAC_SHA512` (extract + expand per RFC 5869) |
644
- | `Fortuna` | `Serpent` + `SHA256` |
733
+ |TS Class / Object|Composes|
734
+ |---|---|
735
+ |`SerpentCipher`|`SerpentCbc` + `HMAC_SHA256` + `HKDF_SHA256`|
736
+ |`XChaCha20Cipher`|`ChaCha20Poly1305` (via `ops.ts`) + `HKDF_SHA256`|
737
+ |`Seal`|`SealStream` + `OpenStream` (degenerate single-chunk case)|
738
+ |`SealStream`|`CipherSuite` (generic — caller provides cipher)|
739
+ |`OpenStream`|`CipherSuite` (generic — caller provides cipher)|
740
+ |`SealStreamPool`|`CipherSuite` + `compileWasm()` + Web Workers|
741
+ |`HKDF_SHA256`|`HMAC_SHA256` (extract + expand per RFC 5869)|
742
+ |`HKDF_SHA512`|`HMAC_SHA512` (extract + expand per RFC 5869)|
743
+ |`Fortuna`|`Generator` + `HashFn` (any compatible pair: `SerpentGenerator`/`ChaCha20Generator` × `SHA256Hash`/`SHA3_256Hash`)|
645
744
 
646
745
  ---
647
746
 
648
747
  ### Cross-module dependencies
649
748
 
650
- | Relationship | Notes |
651
- |-------------|-------|
652
- | `SerpentCipher` → `serpent` + `sha2` | Tier 2 composition: Serpent-CBC + HMAC-SHA256 + HKDF-SHA256. |
653
- | `XChaCha20Cipher` → `chacha20` + `sha2` | HKDF-SHA256 for key derivation + HChaCha20 + ChaCha20-Poly1305 for per-chunk AEAD. |
654
- | `KyberSuite` → `kyber` + `sha3` + inner cipher | KEM encaps/decaps + HKDF with kemCt binding + inner CipherSuite. |
655
- | `SealStream`, `OpenStream` → depends on cipher | Cipher-agnostic. Module requirements are determined by the `CipherSuite` passed at construction. |
656
- | `SealStreamPool` → depends on cipher | Same module requirements as the cipher, plus `WasmSource` in pool opts for worker compilation. |
657
- | `Fortuna` → `serpent` + `sha2` | Uses `Fortuna.create()` static factory instead of `new`. |
658
- | `MlKem512`, `MlKem768`, `MlKem1024` → `kyber` + `sha3` | Kyber module handles polynomial arithmetic; sha3 provides SHAKE128/256, SHA3-256/512 for G/H/J/matrix gen. |
659
- | `HKDF_SHA256`, `HKDF_SHA512` → `sha2` | Pure TS composition — extract and expand steps per RFC 5869. |
660
- | All other classes | Each depends on exactly **one** WASM module. |
749
+ |Relationship|Notes|
750
+ |---|---|
751
+ |`SerpentCipher` → `serpent` + `sha2`|Tier 2 composition: Serpent-CBC + HMAC-SHA256 + HKDF-SHA256.|
752
+ |`XChaCha20Cipher` → `chacha20` + `sha2`|HKDF-SHA256 for key derivation + HChaCha20 + ChaCha20-Poly1305 for per-chunk AEAD.|
753
+ |`KyberSuite` → `kyber` + `sha3` + inner cipher|KEM encaps/decaps + HKDF with kemCt binding + inner CipherSuite.|
754
+ |`SealStream`, `OpenStream` → depends on cipher|Cipher-agnostic. Module requirements are determined by the `CipherSuite` passed at construction.|
755
+ |`SealStreamPool` → depends on cipher|Same module requirements as the cipher, plus `WasmSource` in pool opts for worker compilation.|
756
+ |`Fortuna` → cipher module + hash module|Uses `Fortuna.create({ generator, hash })` static factory instead of `new`. Required modules depend on which generator and hash you pass. See [fortuna.md](./fortuna.md).|
757
+ |`MlKem512`, `MlKem768`, `MlKem1024` → `kyber` + `sha3`|Kyber module handles polynomial arithmetic; sha3 provides SHAKE128/256, SHA3-256/512 for G/H/J/matrix gen.|
758
+ |`HKDF_SHA256`, `HKDF_SHA512` → `sha2`|Pure TS composition — extract and expand steps per RFC 5869.|
759
+ |All other classes|Each depends on exactly **one** WASM module.|
661
760
 
662
761
  ---
663
762
 
664
763
  ### Public API barrel (`src/ts/index.ts`)
665
764
 
666
- The root barrel defines and exports the dispatching `init()` function.
667
- It is the only file that imports all four module-scoped init functions.
668
-
669
- | Source | Exports |
670
- |--------|------------|
671
- | *(barrel itself)* | `init` (dispatching function — calls per-module init functions via `Promise.all`) |
672
- | `init.ts` | `Module`, `WasmSource`, `isInitialized`, `_resetForTesting` |
673
- | `errors.ts` | `AuthenticationError` |
674
- | `serpent/index.ts` | `Serpent`, `SerpentCtr`, `SerpentCbc`, `SerpentCipher`, `_serpentReady` |
675
- | `chacha20/index.ts` | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, `XChaCha20Cipher`, `_chachaReady` |
676
- | `sha2/index.ts` | `SHA256`, `SHA512`, `SHA384`, `HMAC_SHA256`, `HMAC_SHA512`, `HMAC_SHA384`, `HKDF_SHA256`, `HKDF_SHA512`, `_sha2Ready` |
677
- | `sha3/index.ts` | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256`, `_sha3Ready` |
678
- | `keccak/index.ts` | `keccakInit` + re-exports all sha3 classes (alias subpath) |
679
- | `kyber/index.ts` | `kyberInit`, `KyberSuite`, `MlKem512`, `MlKem768`, `MlKem1024`, `KyberKeyPair`, `KyberEncapsulation`, `KyberParams`, `MLKEM512`, `MLKEM768`, `MLKEM1024` |
680
- | `stream/index.ts` | `Seal`, `SealStream`, `OpenStream`, `SealStreamPool`, `CipherSuite`, `DerivedKeys`, `SealStreamOpts`, `PoolOpts`, `FLAG_FRAMED`, `TAG_DATA`, `TAG_FINAL`, `HEADER_SIZE`, `CHUNK_MIN`, `CHUNK_MAX` |
681
- | `fortuna.ts` | `Fortuna` |
682
- | `types.ts` | `Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD` |
683
- | `utils.ts` | `hexToBytes`, `bytesToHex`, `utf8ToBytes`, `bytesToUtf8`, `base64ToBytes`, `bytesToBase64`, `constantTimeEqual`, `wipe`, `xor`, `concat`, `randomBytes` |
684
-
685
- Each subpath export also exports its own module-specific init function for
686
- tree-shakeable loading: `serpentInit(source)`, `chacha20Init(source)`,
687
- `sha2Init(source)`, `sha3Init(source)`, `keccakInit(source)`.
765
+ The root barrel defines and exports the dispatching `init()` function. It is the only file that imports all four module-scoped init functions.
766
+
767
+ |Source|Exports|
768
+ |---|---|
769
+ |_(barrel itself)_|`init` (dispatching function — calls per-module init functions via `Promise.all`)|
770
+ |`init.ts`|`Module`, `WasmSource`, `isInitialized`|
771
+ |`errors.ts`|`AuthenticationError`|
772
+ |`serpent/index.ts`|`Serpent`, `SerpentCtr`, `SerpentCbc`, `SerpentCipher`, `_serpentReady`|
773
+ |`chacha20/index.ts`|`ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, `XChaCha20Cipher`, `_chachaReady`|
774
+ |`sha2/index.ts`|`SHA256`, `SHA512`, `SHA384`, `HMAC_SHA256`, `HMAC_SHA512`, `HMAC_SHA384`, `HKDF_SHA256`, `HKDF_SHA512`, `_sha2Ready`|
775
+ |`sha3/index.ts`|`SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256`, `_sha3Ready`|
776
+ |`keccak/index.ts`|`keccakInit` + re-exports all sha3 classes (alias subpath)|
777
+ |`kyber/index.ts`|`kyberInit`, `KyberSuite`, `MlKem512`, `MlKem768`, `MlKem1024`, `KyberKeyPair`, `KyberEncapsulation`, `KyberParams`, `MLKEM512`, `MLKEM768`, `MLKEM1024`|
778
+ |`stream/index.ts`|`Seal`, `SealStream`, `OpenStream`, `SealStreamPool`, `CipherSuite`, `DerivedKeys`, `SealStreamOpts`, `PoolOpts`, `FLAG_FRAMED`, `TAG_DATA`, `TAG_FINAL`, `HEADER_SIZE`, `CHUNK_MIN`, `CHUNK_MAX`|
779
+ |`fortuna.ts`|`Fortuna`|
780
+ |`types.ts`|`Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD`, `Generator`, `HashFn`|
781
+ |`utils.ts`|`hexToBytes`, `bytesToHex`, `utf8ToBytes`, `bytesToUtf8`, `base64ToBytes`, `bytesToBase64`, `constantTimeEqual`, `CT_MAX_BYTES`, `wipe`, `xor`, `concat`, `randomBytes`, `hasSIMD`|
782
+
783
+ Each subpath export also exports its own module-specific init function for tree-shakeable loading: `serpentInit(source)`, `chacha20Init(source)`, `sha2Init(source)`, `sha3Init(source)`, `keccakInit(source)`.
688
784
 
689
785
  ---
690
786
 
691
787
  ## npm Package
692
788
 
693
789
  **Subpath exports:**
790
+
694
791
  ```json
695
792
  {
696
793
  "exports": {
@@ -707,17 +804,14 @@ tree-shakeable loading: `serpentInit(source)`, `chacha20Init(source)`,
707
804
  "./keccak": "./dist/keccak/index.js",
708
805
  "./keccak/embedded": "./dist/keccak/embedded.js",
709
806
  "./kyber": "./dist/kyber/index.js",
710
- "./kyber/embedded": "./dist/kyber/embedded.js"
807
+ "./kyber/embedded": "./dist/kyber/embedded.js",
808
+ "./ratchet": "./dist/ratchet/index.js"
711
809
  }
712
810
  }
713
811
  ```
714
812
 
715
813
  > [!NOTE]
716
- > Pool worker files (`dist/serpent/pool-worker.js`, `dist/chacha20/pool-worker.js`)
717
- > ship in the package but are not in the `exports` map. They are internal Web
718
- > Worker entry points loaded by `SealStreamPool` at runtime via
719
- > `new URL('./pool-worker.js', import.meta.url)`. Do not import them as named
720
- > subpaths.
814
+ > Pool worker source files (`dist/serpent/pool-worker.js`, `dist/chacha20/pool-worker.js`) ship in the package but are not in the `exports` map. They are the build inputs from which `scripts/embed-workers.ts` produces the IIFE source strings embedded in `dist/<cipher>/cipher-suite.js` at lib build time. Workers are spawned from those embedded strings via classic blob URLs. Consumers do not import the `pool-worker.js` files directly, and bundlers do not need to chunk them. Strict-CSP consumers (`worker-src 'self'`, no `blob:`) can supply their own URL-based factory by spread-overriding `createPoolWorker` on the cipher object; see [ciphersuite.md](./ciphersuite.md).
721
815
 
722
816
  The root `.` export re-exports everything. Subpath exports allow bundlers to tree-shake at the module level; a consumer importing only `./sha3` does not include the Serpent wrapper classes or their embedded WASM binaries in their bundle.
723
817
 
@@ -727,36 +821,35 @@ The `/embedded` subpaths provide gzip+base64 WASM blobs for zero-config usage. C
727
821
 
728
822
  **Published.** The npm package includes:
729
823
 
730
- - `dist/`: compiled JS, TypeScript declarations, WASM binaries, pool worker scripts, and a subset of consumer-facing API docs for offline use.
824
+ - `dist/`: compiled JS, TypeScript declarations, WASM binaries, pool worker source files (build inputs, not runtime spawn entries; see the NOTE above), and a subset of consumer-facing API docs for offline use.
731
825
  - `CLAUDE.md`: agent-facing project context.
732
826
  - `SECURITY.md`: vulnerability disclosure policy.
733
827
 
734
- **Not published.** `src/`, `test/`, `build/`, `scripts/`, `docs/`, `.github/`, editor configs.
828
+ **Not published.** `src/`, `test/`, `build/`, `scripts/`, `.github/`, editor configs.
735
829
 
736
830
  ---
737
831
 
738
832
  ## Buffer Layouts
739
833
 
740
- All offsets start at 0 per module. Independent linear memory. No offsets are
741
- shared or coordinated across modules.
834
+ All offsets start at 0 per module. Independent linear memory. No offsets are shared or coordinated across modules.
742
835
 
743
836
  ### Serpent module (3 pages, 192 KB)
744
837
 
745
838
  Source: `src/asm/serpent/buffers.ts`
746
839
 
747
- | Offset | Size | Name |
748
- |--------|------|------|
749
- | 0 | 32 | `KEY_BUFFER` — key input (padded to 32 bytes for all key sizes) |
750
- | 32 | 16 | `BLOCK_PT_BUFFER` — single block plaintext |
751
- | 48 | 16 | `BLOCK_CT_BUFFER` — single block ciphertext |
752
- | 64 | 16 | `NONCE_BUFFER` — CTR mode nonce |
753
- | 80 | 16 | `COUNTER_BUFFER` — 128-bit little-endian counter |
754
- | 96 | 528 | `SUBKEY_BUFFER` — key schedule output (33 rounds × 4 × 4 bytes) |
755
- | 624 | 65552 | `CHUNK_PT_BUFFER` — streaming plaintext (CTR/CBC); +16 from 65536 to fit PKCS7 max overhead |
756
- | 66176 | 65552 | `CHUNK_CT_BUFFER` — streaming ciphertext (CTR/CBC) |
757
- | 131728 | 20 | `WORK_BUFFER` — 5 × i32 scratch registers (key schedule + S-box/LT rounds) |
758
- | 131748 | 16 | `CBC_IV_BUFFER` — CBC initialization vector / chaining value |
759
- | 131856 | — | END |
840
+ |Offset|Size|Name|
841
+ |---|---|---|
842
+ |0|32|`KEY_BUFFER` — key input (padded to 32 bytes for all key sizes)|
843
+ |32|16|`BLOCK_PT_BUFFER` — single block plaintext|
844
+ |48|16|`BLOCK_CT_BUFFER` — single block ciphertext|
845
+ |64|16|`NONCE_BUFFER` — CTR mode nonce|
846
+ |80|16|`COUNTER_BUFFER` — 128-bit little-endian counter|
847
+ |96|528|`SUBKEY_BUFFER` — key schedule output (33 rounds × 4 × 4 bytes)|
848
+ |624|65552|`CHUNK_PT_BUFFER` — streaming plaintext (CTR/CBC); +16 from 65536 to fit PKCS7 max overhead|
849
+ |66176|65552|`CHUNK_CT_BUFFER` — streaming ciphertext (CTR/CBC)|
850
+ |131728|20|`WORK_BUFFER` — 5 × i32 scratch registers (key schedule + S-box/LT rounds)|
851
+ |131748|16|`CBC_IV_BUFFER` — CBC initialization vector / chaining value|
852
+ |131856|—|END|
760
853
 
761
854
  `wipeBuffers()` zeroes all 10 buffers (key, block pt/ct, nonce, counter, subkeys, work, chunk pt/ct, CBC IV).
762
855
 
@@ -764,29 +857,29 @@ Source: `src/asm/serpent/buffers.ts`
764
857
 
765
858
  Source: `src/asm/chacha20/buffers.ts`
766
859
 
767
- | Offset | Size | Name |
768
- |--------|------|------|
769
- | 0 | 32 | `KEY_BUFFER` — ChaCha20 256-bit key |
770
- | 32 | 12 | `CHACHA_NONCE_BUFFER` — 96-bit nonce (3 × u32, LE) |
771
- | 44 | 4 | `CHACHA_CTR_BUFFER` — u32 block counter |
772
- | 48 | 64 | `CHACHA_BLOCK_BUFFER` — 64-byte keystream block output |
773
- | 112 | 64 | `CHACHA_STATE_BUFFER` — 16 × u32 initial state |
774
- | 176 | 65536 | `CHUNK_PT_BUFFER` — streaming plaintext |
775
- | 65712 | 65536 | `CHUNK_CT_BUFFER` — streaming ciphertext |
776
- | 131248 | 32 | `POLY_KEY_BUFFER` — one-time key r‖s |
777
- | 131280 | 64 | `POLY_MSG_BUFFER` — message staging (≤ 64 bytes per polyUpdate) |
778
- | 131344 | 16 | `POLY_BUF_BUFFER` — partial block accumulator |
779
- | 131360 | 4 | `POLY_BUF_LEN_BUFFER` — u32 bytes in partial block |
780
- | 131364 | 16 | `POLY_TAG_BUFFER` — 16-byte output MAC tag |
781
- | 131380 | 40 | `POLY_H_BUFFER` — accumulator h: 5 × u64 |
782
- | 131420 | 40 | `POLY_R_BUFFER` — clamped r: 5 × u64 |
783
- | 131460 | 32 | `POLY_RS_BUFFER` — precomputed 5×r[1..4]: 4 × u64 |
784
- | 131492 | 16 | `POLY_S_BUFFER` — s pad: 4 × u32 |
785
- | 131508 | 24 | `XCHACHA_NONCE_BUFFER` — full 24-byte XChaCha20 nonce |
786
- | 131532 | 32 | `XCHACHA_SUBKEY_BUFFER` — HChaCha20 output (key material) |
787
- | 131564 | 4 | *(padding for 16-byte SIMD alignment)* |
788
- | 131568 | 256 | `CHACHA_SIMD_WORK_BUFFER` — 4-wide inter-block keystream (4 × 64 bytes) |
789
- | 131824 | — | END |
860
+ |Offset|Size|Name|
861
+ |---|---|---|
862
+ |0|32|`KEY_BUFFER` — ChaCha20 256-bit key|
863
+ |32|12|`CHACHA_NONCE_BUFFER` — 96-bit nonce (3 × u32, LE)|
864
+ |44|4|`CHACHA_CTR_BUFFER` — u32 block counter|
865
+ |48|64|`CHACHA_BLOCK_BUFFER` — 64-byte keystream block output|
866
+ |112|64|`CHACHA_STATE_BUFFER` — 16 × u32 initial state|
867
+ |176|65536|`CHUNK_PT_BUFFER` — streaming plaintext|
868
+ |65712|65536|`CHUNK_CT_BUFFER` — streaming ciphertext|
869
+ |131248|32|`POLY_KEY_BUFFER` — one-time key r‖s|
870
+ |131280|64|`POLY_MSG_BUFFER` — message staging (≤ 64 bytes per polyUpdate)|
871
+ |131344|16|`POLY_BUF_BUFFER` — partial block accumulator|
872
+ |131360|4|`POLY_BUF_LEN_BUFFER` — u32 bytes in partial block|
873
+ |131364|16|`POLY_TAG_BUFFER` — 16-byte output MAC tag|
874
+ |131380|40|`POLY_H_BUFFER` — accumulator h: 5 × u64|
875
+ |131420|40|`POLY_R_BUFFER` — clamped r: 5 × u64|
876
+ |131460|32|`POLY_RS_BUFFER` — precomputed 5×r[1..4]: 4 × u64|
877
+ |131492|16|`POLY_S_BUFFER` — s pad: 4 × u32|
878
+ |131508|24|`XCHACHA_NONCE_BUFFER` — full 24-byte XChaCha20 nonce|
879
+ |131532|32|`XCHACHA_SUBKEY_BUFFER` — HChaCha20 output (key material)|
880
+ |131564|4|_(padding for 16-byte SIMD alignment)_|
881
+ |131568|256|`CHACHA_SIMD_WORK_BUFFER` — 4-wide inter-block keystream (4 × 64 bytes)|
882
+ |131824|—|END|
790
883
 
791
884
  `wipeBuffers()` zeroes all 15 buffer regions (key, chacha nonce/ctr/block/state, chunk pt/ct, poly key/msg/buf/tag/h/r/rs/s, xchacha nonce/subkey, SIMD work).
792
885
 
@@ -794,29 +887,29 @@ Source: `src/asm/chacha20/buffers.ts`
794
887
 
795
888
  Source: `src/asm/sha2/buffers.ts`
796
889
 
797
- | Offset | Size | Name |
798
- |--------|------|------|
799
- | 0 | 32 | `SHA256_H` — SHA-256 hash state H0..H7 (8 × u32) |
800
- | 32 | 64 | `SHA256_BLOCK` — SHA-256 block accumulator |
801
- | 96 | 256 | `SHA256_W` — SHA-256 message schedule W[0..63] (64 × u32) |
802
- | 352 | 32 | `SHA256_OUT` — SHA-256 digest output |
803
- | 384 | 64 | `SHA256_INPUT` — SHA-256 user input staging (one block) |
804
- | 448 | 4 | `SHA256_PARTIAL` — u32 partial block length |
805
- | 452 | 8 | `SHA256_TOTAL` — u64 total bytes hashed |
806
- | 460 | 64 | `HMAC256_IPAD` — HMAC-SHA256 K' XOR ipad |
807
- | 524 | 64 | `HMAC256_OPAD` — HMAC-SHA256 K' XOR opad |
808
- | 588 | 32 | `HMAC256_INNER` — HMAC-SHA256 inner hash |
809
- | 620 | 64 | `SHA512_H` — SHA-512 hash state H0..H7 (8 × u64) |
810
- | 684 | 128 | `SHA512_BLOCK` — SHA-512 block accumulator |
811
- | 812 | 640 | `SHA512_W` — SHA-512 message schedule W[0..79] (80 × u64) |
812
- | 1452 | 64 | `SHA512_OUT` — SHA-512 digest output (SHA-384 uses first 48 bytes) |
813
- | 1516 | 128 | `SHA512_INPUT` — SHA-512 user input staging (one block) |
814
- | 1644 | 4 | `SHA512_PARTIAL` — u32 partial block length |
815
- | 1648 | 8 | `SHA512_TOTAL` — u64 total bytes hashed |
816
- | 1656 | 128 | `HMAC512_IPAD` — HMAC-SHA512 K' XOR ipad (128-byte block size) |
817
- | 1784 | 128 | `HMAC512_OPAD` — HMAC-SHA512 K' XOR opad |
818
- | 1912 | 64 | `HMAC512_INNER` — HMAC-SHA512 inner hash |
819
- | 1976 | — | END |
890
+ |Offset|Size|Name|
891
+ |---|---|---|
892
+ |0|32|`SHA256_H` — SHA-256 hash state H0..H7 (8 × u32)|
893
+ |32|64|`SHA256_BLOCK` — SHA-256 block accumulator|
894
+ |96|256|`SHA256_W` — SHA-256 message schedule W[0..63] (64 × u32)|
895
+ |352|32|`SHA256_OUT` — SHA-256 digest output|
896
+ |384|64|`SHA256_INPUT` — SHA-256 user input staging (one block)|
897
+ |448|4|`SHA256_PARTIAL` — u32 partial block length|
898
+ |452|8|`SHA256_TOTAL` — u64 total bytes hashed|
899
+ |460|64|`HMAC256_IPAD` — HMAC-SHA256 K' XOR ipad|
900
+ |524|64|`HMAC256_OPAD` — HMAC-SHA256 K' XOR opad|
901
+ |588|32|`HMAC256_INNER` — HMAC-SHA256 inner hash|
902
+ |620|64|`SHA512_H` — SHA-512 hash state H0..H7 (8 × u64)|
903
+ |684|128|`SHA512_BLOCK` — SHA-512 block accumulator|
904
+ |812|640|`SHA512_W` — SHA-512 message schedule W[0..79] (80 × u64)|
905
+ |1452|64|`SHA512_OUT` — SHA-512 digest output (SHA-384 uses first 48 bytes)|
906
+ |1516|128|`SHA512_INPUT` — SHA-512 user input staging (one block)|
907
+ |1644|4|`SHA512_PARTIAL` — u32 partial block length|
908
+ |1648|8|`SHA512_TOTAL` — u64 total bytes hashed|
909
+ |1656|128|`HMAC512_IPAD` — HMAC-SHA512 K' XOR ipad (128-byte block size)|
910
+ |1784|128|`HMAC512_OPAD` — HMAC-SHA512 K' XOR opad|
911
+ |1912|64|`HMAC512_INNER` — HMAC-SHA512 inner hash|
912
+ |1976|—|END|
820
913
 
821
914
  `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).
822
915
 
@@ -824,15 +917,15 @@ Source: `src/asm/sha2/buffers.ts`
824
917
 
825
918
  Source: `src/asm/sha3/buffers.ts`
826
919
 
827
- | Offset | Size | Name |
828
- |--------|------|------|
829
- | 0 | 200 | `KECCAK_STATE` 25 × u64 Keccak-f[1600] lane matrix (5×5, row-major x+5y) |
830
- | 200 | 4 | `KECCAK_RATE` u32 rate in bytes (variant-specific: 72–168) |
831
- | 204 | 4 | `KECCAK_ABSORBED` u32 bytes absorbed into current block |
832
- | 208 | 1 | `KECCAK_DSBYTE` u8 domain separation byte (0x06 for SHA-3, 0x1f for SHAKE) |
833
- | 209 | 168 | `KECCAK_INPUT` input staging buffer (max rate = SHAKE128 at 168 bytes) |
834
- | 377 | 168 | `KECCAK_OUT` output buffer (one SHAKE128 squeeze block) |
835
- | 545 | — | END |
920
+ |Offset|Size|Name|
921
+ |---|---|---|
922
+ |0|200|`KECCAK_STATE`: 25 × u64 Keccak-f[1600] lane matrix (5×5, row-major x+5y)|
923
+ |200|4|`KECCAK_RATE`: u32 rate in bytes (variant-specific: 72–168)|
924
+ |204|4|`KECCAK_ABSORBED`: u32 bytes absorbed into current block|
925
+ |208|1|`KECCAK_DSBYTE`: u8 domain separation byte (0x06 for SHA-3, 0x1f for SHAKE)|
926
+ |209|168|`KECCAK_INPUT`: input staging buffer (max rate = SHAKE128 at 168 bytes)|
927
+ |377|168|`KECCAK_OUT`: output buffer (one SHAKE128 squeeze block)|
928
+ |545|—|END|
836
929
 
837
930
  `wipeBuffers()` zeroes all 6 buffer regions (state, rate, absorbed, dsbyte, input, output).
838
931
 
@@ -840,19 +933,19 @@ Source: `src/asm/sha3/buffers.ts`
840
933
 
841
934
  Source: `src/asm/kyber/`
842
935
 
843
- | Region | Offset | Size | Purpose |
844
- |--------|--------|------|---------|
845
- | AS data segment | 0 | 4096 | Zetas table (128 × i16, bit-reversed Montgomery domain) |
846
- | Poly slots | 4096 | 5120 | 10 × 512B scratch polynomials (256 × i16 each) |
847
- | Polyvec slots | 9216 | 16384 | 8 × 2048B scratch polyvecs (k=4 max: 4 × 512B) |
848
- | SEED buffer | 25600 | 32 | Seed ρ/σ |
849
- | MSG buffer | 25632 | 32 | Message / shared secret |
850
- | PK buffer | 25664 | 1568 | Encapsulation key (max k=4) |
851
- | SK buffer | 27232 | 1536 | IND-CPA secret key (max k=4) |
852
- | CT buffer | 28768 | 1568 | Ciphertext (max k=4) |
853
- | CT_PRIME buffer | 30336 | 1568 | Decaps re-encrypt comparison (max k=4) |
854
- | XOF/PRF buffer | 31904 | 1024 | SHAKE squeeze output for rej_uniform / CBD |
855
- | Poly accumulator | 32928 | 512 | Internal scratch for polyvec_basemul_acc |
936
+ |Region|Offset|Size|Purpose|
937
+ |---|---|---|---|
938
+ |AS data segment|0|4096|Zetas table (128 × i16, bit-reversed Montgomery domain)|
939
+ |Poly slots|4096|5120|10 × 512B scratch polynomials (256 × i16 each)|
940
+ |Polyvec slots|9216|16384|8 × 2048B scratch polyvecs (k=4 max: 4 × 512B)|
941
+ |SEED buffer|25600|32|Seed ρ/σ|
942
+ |MSG buffer|25632|32|Message / shared secret|
943
+ |PK buffer|25664|1568|Encapsulation key (max k=4)|
944
+ |SK buffer|27232|1536|IND-CPA secret key (max k=4)|
945
+ |CT buffer|28768|1568|Ciphertext (max k=4)|
946
+ |CT_PRIME buffer|30336|1568|Decaps re-encrypt comparison (max k=4)|
947
+ |XOF/PRF buffer|31904|1024|SHAKE squeeze output for rej_uniform / CBD|
948
+ |Poly accumulator|32928|512|Internal scratch for polyvec_basemul_acc|
856
949
 
857
950
  Total mutable: 29344 bytes (4096–33440). End = 33440 < 192 KB.
858
951
 
@@ -862,14 +955,14 @@ Total mutable: 29344 bytes (4096–33440). End = 33440 < 192 KB.
862
955
 
863
956
  ## Test Suite
864
957
 
865
- ### Structure
866
958
 
959
+ ### Structure
867
960
 
868
961
  For the full testing methodology and vector corpus, see: [test-suite.md](./test-suite.md)
869
962
 
870
963
  ### Gate discipline
871
964
 
872
- Each primitive family has a gate test: the simplest authoritative vector for that primitive. The gate must pass before any other tests in that family are written or run. Gate tests are annotated with a `// GATE` comment.
965
+ **Each primitive family has a gate test:** the simplest authoritative vector for that primitive. The gate must pass before any other tests in that family are written or run. Gate tests are annotated with a `// GATE` comment.
873
966
 
874
967
  ### `init.test.ts` contracts
875
968
 
@@ -882,51 +975,37 @@ Each primitive family has a gate test: the simplest authoritative vector for tha
882
975
 
883
976
  ## Correctness Contract
884
977
 
885
- leviathan-crypto must produce byte-identical output to the authoritative
886
- specification for every known test vector. Cross-checks against the leviathan
887
- TypeScript reference and external tools (OpenSSL, Python hashlib, Node.js crypto)
888
- provide additional verification layers.
978
+ leviathan-crypto must produce byte-identical output to the authoritative specification for every known test vector. Cross-checks against the leviathan TypeScript reference and external tools (OpenSSL, Python hashlib, Node.js crypto) provide additional verification layers.
889
979
 
890
- The test vector corpus in `test/vectors/` is read-only. Integrity is verified via
891
- `SHA256SUMS`, expected values are sourced directly from authoritative references.
892
- They are the immutable truth, and must never be modified to make tests pass.
980
+ The vector corpus in `test/vectors/` act as a source of immutable known-answer-test truth. KAT files are reference data from authoritative specifications (FIPS, RFCs, ACVP, NIST CAVP, NESSIE) or are self generated as regression vectors by `scripts/gen-*-vectors.ts`. CI validates integrity against `SHA256SUMS`. See [test-suite.md](./test-suite.md) for the full corpus inventory, provenance, and gate discipline.
893
981
 
894
982
  ---
895
983
 
896
984
  ## Known Limitations
897
985
 
898
- - **`SerpentCbc` is unauthenticated**: use `Seal` with `SerpentCipher` for
899
- authenticated Serpent encryption, or pair `SerpentCbc` with `HMAC_SHA256`
900
- (Encrypt-then-MAC) if direct CBC access is required.
901
- - **Single-threaded WASM per instance**: one WASM instance per binary per thread.
902
- `SealStreamPool` provides Worker-based parallelism for both cipher families;
903
- other primitives remain single-threaded.
904
- - **Max input per WASM call**: CTR accepts at most 65536 bytes per call; CBC
905
- accepts at most 65552 bytes (65536 + 16 bytes PKCS7 maximum overhead).
906
- Wrappers handle splitting automatically for larger inputs.
907
- - **WASM side-channel posture**: WebAssembly implementations offer the best
908
- available side-channel resistance (branchless, table-free), but lack
909
- hardware-level constant-time guarantees. For applications where timing
910
- side channels are a primary threat, a native cryptographic library with
911
- verified constant-time guarantees will be more appropriate than any
912
- WASM-based implementation.
986
+ - **`SerpentCbc` is unauthenticated**: use `Seal` with `SerpentCipher` for authenticated Serpent encryption, or pair `SerpentCbc` with `HMAC_SHA256` (Encrypt-then-MAC) if direct CBC access is required.
987
+ - **Single-threaded WASM per instance**: one WASM instance per binary per thread. `SealStreamPool` provides Worker-based parallelism for both cipher families; other primitives remain single-threaded.
988
+ - **Max input per WASM call**: CTR accepts at most 65536 bytes per call; CBC accepts at most 65552 bytes (65536 + 16 bytes PKCS7 maximum overhead). Wrappers handle splitting automatically for larger inputs.
989
+ - **WASM side-channel posture**: WebAssembly implementations offer the best available side-channel resistance (branchless, table-free), but lack hardware-level constant-time guarantees. For applications where timing side channels are a primary threat, a native cryptographic library with verified constant-time guarantees will be more appropriate than any WASM-based implementation.
913
990
 
914
991
  ---
915
992
 
916
- > ## Cross-References
917
- >
918
- > - [index](./README.md) — Project Documentation index
919
- > - [lexicon](./lexicon.md) — Glossary of cryptographic terms
920
- > - [test-suite](./test-suite.md) testing methodology, vector corpus, and gate discipline
921
- > - [init](./init.md) `init()` API, `WasmSource`, and idempotent behavior
922
- > - [loader](./loader.md) internal WASM binary loading strategies
923
- > - [wasm](./wasm.md) — WebAssembly primer: modules, instances, memory, and the init gate
924
- > - [types](./types.md) public TypeScript interfaces and `CipherSuite`
925
- > - [utils](./utils.md) encoding helpers, `constantTimeEqual`, `wipe`, `randomBytes`
926
- > - [authenticated encryption](./aead.md) SealStream, OpenStream, SealStreamPool, wire format
927
- > - [serpent](./serpent.md) Serpent-256 TypeScript API, SerpentCipher
928
- > - [chacha20](./chacha20.md) ChaCha20/Poly1305 TypeScript API, XChaCha20Cipher
929
- > - [sha2](./sha2.md) — SHA-2 hashes, HMAC, and HKDF TypeScript API
930
- > - [sha3](./sha3.md) — SHA-3 hashes and SHAKE XOFs TypeScript API
931
- > - [fortuna](./fortuna.md) Fortuna CSPRNG with forward secrecy and entropy pooling
932
- > - [argon2id](./argon2id.md) Argon2id password hashing and key derivation
993
+ ## Cross-References
994
+
995
+ |Document|Description|
996
+ |---|---|
997
+ |[index](./README.md)|Project Documentation index|
998
+ |[lexicon](./lexicon.md)|Glossary of cryptographic terms|
999
+ |[test-suite](./test-suite.md)|testing methodology, vector corpus, and gate discipline|
1000
+ |[init](./init.md)|`init()` API, `WasmSource`, and idempotent behavior|
1001
+ |[loader](./loader.md)|internal WASM binary loading strategies|
1002
+ |[wasm](./wasm.md)|WebAssembly primer: modules, instances, memory, and the init gate|
1003
+ |[types](./types.md)|public TypeScript interfaces and `CipherSuite`|
1004
+ |[utils](./utils.md)|encoding helpers, `constantTimeEqual`, `wipe`, `randomBytes`|
1005
+ |[authenticated encryption](./aead.md)|`Seal`, `SealStream`, `OpenStream`: cipher-agnostic AEAD APIs using a `CipherSuite` such as `SerpentCipher` or `XChaCha20Cipher`|
1006
+ |[serpent](./serpent.md)|Serpent-256 TypeScript API, SerpentCipher|
1007
+ |[chacha20](./chacha20.md)|ChaCha20/Poly1305 TypeScript API, XChaCha20Cipher|
1008
+ |[sha2](./sha2.md)|SHA-2 hashes, HMAC, and HKDF TypeScript API|
1009
+ |[sha3](./sha3.md)|SHA-3 hashes and SHAKE XOFs TypeScript API|
1010
+ |[fortuna](./fortuna.md)|Fortuna CSPRNG with forward secrecy and entropy pooling|
1011
+ |[argon2id](./argon2id.md)|Argon2id password hashing and key derivation|