leviathan-crypto 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CLAUDE.md +265 -0
  2. package/LICENSE +21 -0
  3. package/README.md +322 -0
  4. package/SECURITY.md +174 -0
  5. package/dist/chacha.wasm +0 -0
  6. package/dist/chacha20/index.d.ts +49 -0
  7. package/dist/chacha20/index.js +177 -0
  8. package/dist/chacha20/ops.d.ts +16 -0
  9. package/dist/chacha20/ops.js +146 -0
  10. package/dist/chacha20/pool.d.ts +52 -0
  11. package/dist/chacha20/pool.js +188 -0
  12. package/dist/chacha20/pool.worker.d.ts +1 -0
  13. package/dist/chacha20/pool.worker.js +37 -0
  14. package/dist/chacha20/types.d.ts +30 -0
  15. package/dist/chacha20/types.js +1 -0
  16. package/dist/docs/architecture.md +795 -0
  17. package/dist/docs/argon2id.md +290 -0
  18. package/dist/docs/chacha20.md +602 -0
  19. package/dist/docs/chacha20_pool.md +306 -0
  20. package/dist/docs/fortuna.md +322 -0
  21. package/dist/docs/init.md +308 -0
  22. package/dist/docs/loader.md +206 -0
  23. package/dist/docs/serpent.md +914 -0
  24. package/dist/docs/sha2.md +620 -0
  25. package/dist/docs/sha3.md +509 -0
  26. package/dist/docs/types.md +198 -0
  27. package/dist/docs/utils.md +273 -0
  28. package/dist/docs/wasm.md +193 -0
  29. package/dist/embedded/chacha.d.ts +1 -0
  30. package/dist/embedded/chacha.js +2 -0
  31. package/dist/embedded/serpent.d.ts +1 -0
  32. package/dist/embedded/serpent.js +2 -0
  33. package/dist/embedded/sha2.d.ts +1 -0
  34. package/dist/embedded/sha2.js +2 -0
  35. package/dist/embedded/sha3.d.ts +1 -0
  36. package/dist/embedded/sha3.js +2 -0
  37. package/dist/fortuna.d.ts +72 -0
  38. package/dist/fortuna.js +445 -0
  39. package/dist/index.d.ts +13 -0
  40. package/dist/index.js +44 -0
  41. package/dist/init.d.ts +11 -0
  42. package/dist/init.js +49 -0
  43. package/dist/loader.d.ts +4 -0
  44. package/dist/loader.js +30 -0
  45. package/dist/serpent/index.d.ts +65 -0
  46. package/dist/serpent/index.js +242 -0
  47. package/dist/serpent/seal.d.ts +8 -0
  48. package/dist/serpent/seal.js +70 -0
  49. package/dist/serpent/stream-encoder.d.ts +20 -0
  50. package/dist/serpent/stream-encoder.js +167 -0
  51. package/dist/serpent/stream-pool.d.ts +48 -0
  52. package/dist/serpent/stream-pool.js +285 -0
  53. package/dist/serpent/stream-sealer.d.ts +34 -0
  54. package/dist/serpent/stream-sealer.js +223 -0
  55. package/dist/serpent/stream.d.ts +28 -0
  56. package/dist/serpent/stream.js +205 -0
  57. package/dist/serpent/stream.worker.d.ts +32 -0
  58. package/dist/serpent/stream.worker.js +117 -0
  59. package/dist/serpent/types.d.ts +5 -0
  60. package/dist/serpent/types.js +1 -0
  61. package/dist/serpent.wasm +0 -0
  62. package/dist/sha2/hkdf.d.ts +16 -0
  63. package/dist/sha2/hkdf.js +108 -0
  64. package/dist/sha2/index.d.ts +40 -0
  65. package/dist/sha2/index.js +190 -0
  66. package/dist/sha2/types.d.ts +5 -0
  67. package/dist/sha2/types.js +1 -0
  68. package/dist/sha2.wasm +0 -0
  69. package/dist/sha3/index.d.ts +55 -0
  70. package/dist/sha3/index.js +246 -0
  71. package/dist/sha3/types.d.ts +5 -0
  72. package/dist/sha3/types.js +1 -0
  73. package/dist/sha3.wasm +0 -0
  74. package/dist/types.d.ts +24 -0
  75. package/dist/types.js +26 -0
  76. package/dist/utils.d.ts +26 -0
  77. package/dist/utils.js +169 -0
  78. package/package.json +90 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,265 @@
1
+ # leviathan-crypto — AI Assistant Guide
2
+
3
+ This file ships with the package to help AI assistants use this library correctly.
4
+ Full API documentation is in the `docs/` directory alongside this file.
5
+
6
+ ---
7
+
8
+ ## What This Library Is
9
+
10
+ `leviathan-crypto` is a zero-dependency WebAssembly cryptography library for
11
+ TypeScript and JavaScript. All cryptographic computation runs in WASM, outside
12
+ the JavaScript JIT. The TypeScript layer provides the public API — input
13
+ validation, type safety, and ergonomics. It never implements cryptographic
14
+ algorithms itself.
15
+
16
+ ---
17
+
18
+ ## Critical: `init()` is required
19
+
20
+ **No class works before `init()` is called.** Calling any class before its
21
+ module is loaded throws immediately with a clear error. Call `init()` once at
22
+ startup, before any cryptographic operations.
23
+
24
+ ```typescript
25
+ import { init, SerpentSeal } from 'leviathan-crypto'
26
+
27
+ await init(['serpent', 'sha2']) // load only the modules you need
28
+ ```
29
+
30
+ Available modules: `'serpent'`, `'chacha20'`, `'sha2'`, `'sha3'`
31
+
32
+ ---
33
+
34
+ ## Critical: call `dispose()` after use
35
+
36
+ Every class holds WASM memory containing key material. Call `dispose()` when
37
+ done — it zeroes that memory. Not calling `dispose()` leaks key material.
38
+
39
+ ```typescript
40
+ const cipher = new SerpentSeal()
41
+ try {
42
+ return cipher.encrypt(key, plaintext)
43
+ } finally {
44
+ cipher.dispose()
45
+ }
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Critical: `decrypt()` throws on authentication failure — never returns null
51
+
52
+ All AEAD `decrypt()` methods throw if authentication fails. Do not check for a
53
+ null return — catch the exception.
54
+
55
+ ```typescript
56
+ try {
57
+ const plaintext = seal.decrypt(key, ciphertext)
58
+ } catch {
59
+ // wrong key or tampered data
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Critical: subpath init function names
66
+
67
+ Each subpath export has its own module-specific init function — not `init()`.
68
+ These are only needed for tree-shakeable imports. The root barrel `init()` is
69
+ the normal path.
70
+
71
+ | Subpath | Init function |
72
+ |---------|---------------|
73
+ | `leviathan-crypto/serpent` | `serpentInit()` |
74
+ | `leviathan-crypto/chacha20` | `chacha20Init()` |
75
+ | `leviathan-crypto/sha2` | `sha2Init()` |
76
+ | `leviathan-crypto/sha3` | `sha3Init()` |
77
+
78
+ ```typescript
79
+ // Tree-shakeable — loads only serpent WASM
80
+ import { serpentInit, SerpentSeal } from 'leviathan-crypto/serpent'
81
+ await serpentInit()
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Which module does each class require?
87
+
88
+ | Classes | `init()` call |
89
+ |---------|--------------|
90
+ | `SerpentSeal`, `SerpentStream`, `SerpentStreamPool`, `SerpentStreamSealer`, `SerpentStreamOpener`, `SerpentStreamEncoder`, `SerpentStreamDecoder`, `Serpent`, `SerpentCtr`, `SerpentCbc` | `init(['serpent', 'sha2'])` |
91
+ | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, `XChaCha20Poly1305Pool` | `init(['chacha20'])` |
92
+ | `SHA256`, `SHA384`, `SHA512`, `HMAC_SHA256`, `HMAC_SHA384`, `HMAC_SHA512`, `HKDF_SHA256`, `HKDF_SHA512` | `init(['sha2'])` |
93
+ | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256` | `init(['sha3'])` |
94
+ | `Fortuna` | `init(['serpent', 'sha2'])` |
95
+
96
+ `Argon2id` is a separate subpath: `import { Argon2id } from 'leviathan-crypto/argon2id'`
97
+ It does **not** require `init()` — it uses its own WASM loader.
98
+ `'argon2id'` is **not** a valid module string for `init()`.
99
+
100
+ ---
101
+
102
+ ## Recommended patterns
103
+
104
+ ### Authenticated encryption (recommended default)
105
+
106
+ ```typescript
107
+ import { init, SerpentSeal, randomBytes } from 'leviathan-crypto'
108
+
109
+ await init(['serpent', 'sha2'])
110
+
111
+ const key = randomBytes(64) // 64-byte key (encKey + macKey)
112
+ const seal = new SerpentSeal()
113
+ const ciphertext = seal.encrypt(key, plaintext) // Serpent-CBC + HMAC-SHA256
114
+ const decrypted = seal.decrypt(key, ciphertext) // throws on tamper
115
+ seal.dispose()
116
+ ```
117
+
118
+ ### Incremental streaming AEAD
119
+
120
+ Use when you cannot buffer the full message before encrypting.
121
+
122
+ ```typescript
123
+ import { init, SerpentStreamSealer, SerpentStreamOpener, randomBytes } from 'leviathan-crypto'
124
+
125
+ await init(['serpent', 'sha2'])
126
+
127
+ const key = randomBytes(64)
128
+ const sealer = new SerpentStreamSealer(key, 65536)
129
+ const header = sealer.header() // send to opener before any chunks
130
+
131
+ const chunk0 = sealer.seal(data0) // exactly chunkSize bytes
132
+ const last = sealer.final(tail) // any size up to chunkSize; wipes key
133
+
134
+ const opener = new SerpentStreamOpener(key, header)
135
+ const pt0 = opener.open(chunk0) // throws on auth failure
136
+ const ptLast = opener.open(last)
137
+ ```
138
+
139
+ ### Length-prefixed streaming (for files and buffered transports)
140
+
141
+ `SerpentStreamEncoder`/`SerpentStreamDecoder` wrap the sealer/opener with
142
+ `u32be` length-prefixed framing so chunk boundaries are self-delimiting.
143
+
144
+ ```typescript
145
+ import { init, SerpentStreamEncoder, SerpentStreamDecoder, randomBytes } from 'leviathan-crypto'
146
+
147
+ await init(['serpent', 'sha2'])
148
+
149
+ const key = randomBytes(64)
150
+ const encoder = new SerpentStreamEncoder(key, 65536)
151
+ const header = encoder.header()
152
+
153
+ const frame0 = encoder.encode(data0) // u32be(len) || sealed chunk
154
+ const last = encoder.encodeFinal(tail)
155
+
156
+ const decoder = new SerpentStreamDecoder(key, header)
157
+ const chunks = decoder.feed(frame0) // returns Uint8Array[], throws on auth failure
158
+ ```
159
+
160
+ ### XChaCha20-Poly1305
161
+
162
+ ```typescript
163
+ import { init, XChaCha20Poly1305, randomBytes } from 'leviathan-crypto'
164
+
165
+ await init(['chacha20'])
166
+
167
+ const aead = new XChaCha20Poly1305()
168
+ const key = randomBytes(32)
169
+ const nonce = randomBytes(24)
170
+ const sealed = aead.encrypt(key, nonce, plaintext, aad?) // ciphertext || tag
171
+ const plaintext = aead.decrypt(key, nonce, sealed, aad?) // throws on tamper
172
+ aead.dispose()
173
+ ```
174
+
175
+ Note: `encrypt()` returns ciphertext with the 16-byte Poly1305 tag appended.
176
+ `decrypt()` expects the same concatenated format — not separate ciphertext and tag.
177
+
178
+ ### Hashing
179
+
180
+ ```typescript
181
+ import { init, SHA256, HMAC_SHA256 } from 'leviathan-crypto'
182
+
183
+ await init(['sha2'])
184
+
185
+ const hasher = new SHA256()
186
+ const digest = hasher.hash(data) // returns Uint8Array
187
+ hasher.dispose()
188
+
189
+ const mac = new HMAC_SHA256()
190
+ const tag = mac.hash(key, data)
191
+ mac.dispose()
192
+ ```
193
+
194
+ ### SHAKE (XOF — variable-length output)
195
+
196
+ ```typescript
197
+ import { init, SHAKE128 } from 'leviathan-crypto'
198
+
199
+ await init(['sha3'])
200
+
201
+ const xof = new SHAKE128()
202
+ xof.absorb(data)
203
+ const out1 = xof.squeeze(32) // first 32 bytes of output stream
204
+ const out2 = xof.squeeze(32) // next 32 bytes — contiguous XOF stream
205
+ xof.dispose()
206
+ ```
207
+
208
+ ### Fortuna CSPRNG
209
+
210
+ ```typescript
211
+ import { init, Fortuna } from 'leviathan-crypto'
212
+
213
+ await init(['serpent', 'sha2'])
214
+
215
+ const fortuna = await Fortuna.create() // static factory — not new Fortuna()
216
+ const bytes = fortuna.get(32)
217
+ fortuna.stop()
218
+ ```
219
+
220
+ ---
221
+
222
+ ## `SerpentCbc` arg order
223
+
224
+ IV is the **second** argument, not the third:
225
+
226
+ ```typescript
227
+ cipher.encrypt(key, iv, plaintext) // correct
228
+ cipher.decrypt(key, iv, ciphertext) // correct
229
+ ```
230
+
231
+ `SerpentCbc` is unauthenticated. Always pair with `HMAC_SHA256`
232
+ (Encrypt-then-MAC) or use `SerpentSeal` instead.
233
+
234
+ ---
235
+
236
+ ## Utilities (no `init()` required)
237
+
238
+ ```typescript
239
+ import { hexToBytes, bytesToHex, randomBytes, constantTimeEqual, wipe } from 'leviathan-crypto'
240
+
241
+ // available immediately — no await init() needed
242
+ const key = randomBytes(32)
243
+ const hex = bytesToHex(key)
244
+ const back = hexToBytes(hex)
245
+ const safe = constantTimeEqual(a, b) // constant-time equality — never use ===
246
+ wipe(key) // zero a Uint8Array in place
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Full documentation
252
+
253
+ The complete API reference ships in `docs/` alongside this file:
254
+
255
+ | File | Contents |
256
+ |------|----------|
257
+ | `docs/serpent.md` | `SerpentSeal`, `SerpentStream`, `SerpentStreamPool`, `SerpentStreamSealer`, `SerpentStreamOpener`, `SerpentStreamEncoder`, `SerpentStreamDecoder`, `Serpent`, `SerpentCtr`, `SerpentCbc` |
258
+ | `docs/chacha20.md` | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, `XChaCha20Poly1305Pool` |
259
+ | `docs/sha2.md` | `SHA256`, `SHA384`, `SHA512`, `HMAC_SHA256`, `HMAC_SHA384`, `HMAC_SHA512`, `HKDF_SHA256`, `HKDF_SHA512` |
260
+ | `docs/sha3.md` | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256` |
261
+ | `docs/fortuna.md` | `Fortuna` CSPRNG |
262
+ | `docs/init.md` | `init()` API, loading modes, subpath imports |
263
+ | `docs/utils.md` | Encoding helpers, `constantTimeEqual`, `wipe`, `randomBytes` |
264
+ | `docs/types.md` | `Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD` interfaces |
265
+ | `docs/architecture.md` | Module structure, WASM layer, three-tier design |
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leviathan Crypto Library
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,322 @@
1
+ [![MIT Licensed](https://img.shields.io/github/license/xero/text0wnz?logo=wikiversity&logoColor=979da4&labelColor=262a2e&color=b1a268)](https://github.com/xero/text0wnz/blob/main/LICENSE)
2
+ [![Version](https://img.shields.io/github/package-json/version/xero/leviathan-crypto?labelColor=33383e&logo=npm&&logoColor=979da4&color=6e2aa5)](https://github.com/xero/leviathan-crypto/releases/latest)
3
+ [![GitHub repo size](https://img.shields.io/github/repo-size/xero/leviathan-crypto?labelColor=262a2e&logo=googlecontaineroptimizedos&logoColor=979da4&color=6e2aa5)](https://github.com/xero/leviathan-crypto/)
4
+ [![test suite](https://github.com/xero/leviathan-crypto/actions/workflows/test-suite.yml/badge.svg)](https://github.com/xero/leviathan-crypto/actions/workflows/test-suite.yml)
5
+ [![wiki](https://github.com/xero/leviathan-crypto/actions/workflows/wiki.yml/badge.svg)](https://github.com/xero/leviathan-crypto/wiki)
6
+
7
+ <img src="https://github.com/xero/leviathan-crypto/raw/main/docs/logo.svg" alt="Leviathan logo" width="400">
8
+
9
+ # Leviathan-Crypto: Serpent-256 & XChaCha20-Poly1305 Cryptography for the Web
10
+
11
+ Serpent-256, the most conservative AES finalist, employs 32 rounds and a
12
+ maximum security margin, built to withstand future cryptanalytic advancements.
13
+ Paired with the streamlined brilliance of ChaCha20-Poly1305, and complemented
14
+ by SHA-2 and SHA-3. Two design philosophies, four cryptographic primitives,
15
+ integrated into one coherent API.
16
+
17
+ **WebAssembly (WASM) serves as the correctness layer.** It features spec-driven and
18
+ vector-verified AssemblyScript implementations of Serpent-256, ChaCha20/Poly1305,
19
+ SHA-2, and SHA-3. Each cryptographic primitive is compiled into its own isolated
20
+ binary, executing outside the JavaScript JIT. This ensures no speculative
21
+ optimization affects key material and eliminates data-dependent timing
22
+ vulnerabilities from table lookups.
23
+
24
+ **TypeScript acts as the ergonomics layer.** Fully typed classes, explicit
25
+ `init()` gates, input validation, and authenticated compositions
26
+ ([`SerpentSeal`](https://github.com/xero/leviathan-crypto/wiki/serpent#serpentseal),
27
+ [`SerpentStream`](https://github.com/xero/leviathan-crypto/wiki/serpent#serpentstream),
28
+ [`SerpentStreamSealer`](https://github.com/xero/leviathan-crypto/wiki/serpent#serpentstreamsealer--serpentstreamopener))
29
+ ensure primitives are connected correctly, simplifying development and ensuring
30
+ correctness. Advanced users retain the ability to directly access the raw block
31
+ cipher classes.
32
+
33
+ ## Why Serpent-256
34
+
35
+ Serpent-256, an AES finalist, received more first-place security votes than
36
+ Rijndael from the NIST evaluation committee. It was designed with a larger
37
+ security margin: 32 rounds compared to AES's 10, 12, or 14.
38
+
39
+ While Serpent won on security margin, AES (Rijndael) ultimately won the
40
+ competition due to its performance. Rijndael was selected because speed
41
+ was paramount for the hardware and embedded targets NIST was optimizing for
42
+ in 2001. However, for software running on modern hardware where milliseconds
43
+ of encryption latency are acceptable, this tradeoff is no longer as relevant.
44
+
45
+ **Security Margin.** Serpent has been a target of cryptanalytic research
46
+ since the AES competition. The current state-of-the-art is as follows:
47
+
48
+ - **Best known reduced-round attack:**
49
+ - multidimensional linear cryptanalysis reaching 12 of 32 rounds (Nguyen,
50
+ Wu & Wang, ACISP 2011), less than half the full cipher, requiring 2¹¹⁸
51
+ known plaintexts and 2²²⁸·⁸ time.
52
+ - Multidimensional linear cryptanalysis reaches 12 of 32 rounds (Nguyen,
53
+ Wu & Wang, ACISP 2011), less than half the full cipher. This requires
54
+ 2¹¹⁸ known plaintexts and 2²²⁸·⁸ time. [source](https://personal.ntu.edu.sg/wuhj/research/publications/2011_ACISP_MLC.pdf) & [mirror](https://archive.is/6pwMM)
55
+ - **Best known full-round attack:**
56
+ - biclique cryptanalysis of full 32-round Serpent-256 (de Carvalho & Kowada,
57
+ SBSeg 2020), time complexity 2²⁵⁵·²¹, only 0.79 bits below the 256-bit
58
+ brute-force ceiling of 2²⁵⁶, and requires 2⁸⁸ chosen ciphertexts, making
59
+ it strictly less practical than brute force. For comparison, the analogous
60
+ biclique attack on full-round AES-256 (Bogdanov et al., 2011) reaches
61
+ 2²⁵⁴·⁴. Serpent-256 is marginally harder to attack by this method than AES-256.
62
+ - Biclique cryptanalysis of full 32-round Serpent-256 (de Carvalho & Kowada,
63
+ SBSeg 2020) has a time complexity of 2²⁵⁵·²¹, only 0.79 bits below the 256-bit
64
+ brute-force ceiling of 2²⁵⁶. It requires 2⁸⁸ chosen ciphertexts, making it
65
+ strictly less practical than brute force. For comparison, the analogous biclique
66
+ attack on full-round AES-256 (Bogdanov et al., 2011) reaches 2²⁵⁴·⁴.
67
+ Serpent-256 is marginally harder to attack by this method than AES-256. [source](https://sol.sbc.org.br/index.php/sbseg/article/view/19225/19054) & [mirror](https://archive.is/ZZjrT)
68
+ - Our independent research improved the published result by
69
+ −0.20 bits through systematic search over v position, biclique nibble
70
+ selection, and nabla pair. the best configuration (K31/K17, delta nibble 0,
71
+ nabla nibble 10, v = state 66 nibbles 8+9) achieves 2²⁵⁵·¹⁹ with only 2⁴
72
+ chosen ciphertexts. The K17 nabla result is a new finding not present in
73
+ the published papers.
74
+ - Our independent research improved the published result by −0.20 bits through
75
+ systematic search over v position, biclique nibble selection, and nabla pair.
76
+ The best configuration (K31/K17, delta nibble 0, nabla nibble 10, v = state
77
+ 66 nibbles 8+9) achieves 2²⁵⁵·¹⁹ with only 2⁴ chosen ciphertexts. The K17 nabla
78
+ result is a new finding not present in the published papers. [`biclique_research`](https://github.com/xero/BicliqueFinder/blob/main/biclique-research.md)
79
+
80
+ See: [`serpent_audit.md`](https://github.com/xero/leviathan-crypto/wiki/serpent_audit)
81
+ for the full analysis.
82
+
83
+ **Implementation.** Implementation: Serpent's S-boxes are implemented as Boolean gate
84
+ circuits, meaning there are no table lookups, data-dependent memory access, or
85
+ data-dependent branches. Every bit is processed unconditionally on every block.
86
+ This approach provides the most timing-safe cipher implementation available in a
87
+ JavaScript runtime, where JIT optimization can otherwise introduce observable
88
+ timing variations.
89
+
90
+ **Key Size:** The default API only supports 256-bit keys. The absence of 128 or
91
+ 192-bit variants mitigates the risk of key-size downgrade attacks.
92
+
93
+ ## Primitives
94
+
95
+ | Classes | Module | Auth | Notes |
96
+ |---------|--------|------|-------|
97
+ | `SerpentSeal` | `serpent`, `sha2` | **Yes** | Authenticated encryption: Serpent-CBC + HMAC-SHA256. Recommended for most use cases. |
98
+ | `SerpentStream`, `SerpentStreamPool` | `serpent`, `sha2` | **Yes** | Chunked one-shot AEAD for large payloads. Pool variant parallelises across workers. |
99
+ | `SerpentStreamSealer`, `SerpentStreamOpener` | `serpent`, `sha2` | **Yes** | Incremental streaming AEAD: seal and open one chunk at a time without buffering the full message. |
100
+ | `SerpentStreamEncoder`, `SerpentStreamDecoder` | `serpent`, `sha2` | **Yes** | Length-prefixed framing over SerpentStreamSealer/Opener for flat byte streams (files, buffered TCP). |
101
+ | `Serpent`, `SerpentCtr`, `SerpentCbc` | `serpent` | **No** | Raw ECB, CTR, CBC modes. Pair with HMAC-SHA256 for authentication. |
102
+ | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305` | `chacha20` | Yes (AEAD) | RFC 8439 |
103
+ | `SHA256`, `SHA384`, `SHA512`, `HMAC_SHA256`, `HMAC_SHA384`, `HMAC_SHA512` | `sha2` | -- | FIPS 180-4, RFC 2104 |
104
+ | `HKDF_SHA256`, `HKDF_SHA512` | `sha2` | -- | Key derivation: RFC 5869. Extract-and-expand over HMAC. |
105
+ | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256` | `sha3` | -- | FIPS 202 |
106
+ | `Fortuna` | `fortuna` | -- | Fortuna CSPRNG (Ferguson & Schneier). Requires `Fortuna.create()`. |
107
+
108
+ >[!IMPORTANT]
109
+ > All cryptographic computation runs in WASM (AssemblyScript), isolated outside the JavaScript JIT.
110
+ > The TypeScript layer provides the public API with input validation, type safety, and developer ergonomics.
111
+
112
+ ## Quick Start
113
+
114
+ ### Authenticated encryption with Serpent (recommended)
115
+
116
+ ```typescript
117
+ import { init, SerpentSeal, randomBytes } from 'leviathan-crypto'
118
+
119
+ await init(['serpent', 'sha2'])
120
+
121
+ const key = randomBytes(64) // 64-byte key (encKey + macKey)
122
+
123
+ const seal = new SerpentSeal()
124
+
125
+ // Encrypt and authenticate
126
+ const ciphertext = seal.encrypt(key, plaintext)
127
+
128
+ // Decrypt and verify (throws on tamper)
129
+ const decrypted = seal.decrypt(key, ciphertext)
130
+
131
+ seal.dispose()
132
+ ```
133
+
134
+ ### Incremental streaming AEAD
135
+
136
+ Use `SerpentStreamSealer` when data arrives chunk by chunk and you cannot
137
+ buffer the full message before encrypting.
138
+
139
+ ```typescript
140
+ import { init, SerpentStreamSealer, SerpentStreamOpener, randomBytes } from 'leviathan-crypto'
141
+
142
+ await init(['serpent', 'sha2'])
143
+
144
+ const key = randomBytes(64)
145
+
146
+ // Seal side
147
+ const sealer = new SerpentStreamSealer(key, 65536)
148
+ const header = sealer.header() // transmit to opener before any chunks
149
+
150
+ const chunk0 = sealer.seal(data0) // exactly chunkSize bytes
151
+ const chunk1 = sealer.seal(data1)
152
+ const last = sealer.final(tail) // any size up to chunkSize; wipes key on return
153
+
154
+ // Open side
155
+ const opener = new SerpentStreamOpener(key, header)
156
+
157
+ const pt0 = opener.open(chunk0)
158
+ const pt1 = opener.open(chunk1)
159
+ const ptN = opener.open(last) // detects final chunk; wipes key on return
160
+
161
+ // Reordering, truncation, and cross-stream splicing all throw on open()
162
+ ```
163
+
164
+ ### Large payload chunking
165
+
166
+ ```typescript
167
+ import { init, SerpentStream, randomBytes } from 'leviathan-crypto'
168
+
169
+ await init(['serpent', 'sha2'])
170
+
171
+ const key = randomBytes(32) // 32-byte key (HKDF handles expansion internally)
172
+
173
+ const stream = new SerpentStream()
174
+ const ciphertext = stream.seal(key, largePlaintext) // default 64KB chunks
175
+ const decrypted = stream.open(key, ciphertext)
176
+
177
+ stream.dispose()
178
+ ```
179
+
180
+ ### Fortuna CSPRNG
181
+
182
+ ```typescript
183
+ import { init, Fortuna } from 'leviathan-crypto'
184
+
185
+ await init(['serpent', 'sha2'])
186
+
187
+ const fortuna = await Fortuna.create()
188
+ const random = fortuna.get(32) // 32 random bytes
189
+
190
+ fortuna.stop()
191
+ ```
192
+
193
+ ### Hashing with SHA-3
194
+
195
+ ```typescript
196
+ import { init, SHA3_256 } from 'leviathan-crypto'
197
+
198
+ await init(['sha3'])
199
+
200
+ const hasher = new SHA3_256()
201
+ const digest = hasher.hash(new TextEncoder().encode('hello'))
202
+ // digest is a 32-byte Uint8Array
203
+
204
+ hasher.dispose()
205
+ ```
206
+
207
+ ## Utilities
208
+
209
+ These helpers are available immediately on import: no `init()` required.
210
+
211
+ | Function | Description |
212
+ |----------|-------------|
213
+ | `hexToBytes(hex)` | Hex string to `Uint8Array` (accepts uppercase, `0x` prefix) |
214
+ | `bytesToHex(bytes)` | `Uint8Array` to lowercase hex string |
215
+ | `utf8ToBytes(str)` | UTF-8 string to `Uint8Array` |
216
+ | `bytesToUtf8(bytes)` | `Uint8Array` to UTF-8 string |
217
+ | `base64ToBytes(b64)` | Base64/base64url string to `Uint8Array` (undefined on invalid) |
218
+ | `bytesToBase64(bytes, url?)` | `Uint8Array` to base64 string (url=true for base64url) |
219
+ | `constantTimeEqual(a, b)` | Constant-time byte comparison (XOR-accumulate) |
220
+ | `wipe(data)` | Zero a typed array in place |
221
+ | `xor(a, b)` | XOR two equal-length `Uint8Array`s |
222
+ | `concat(a, b)` | Concatenate two `Uint8Array`s |
223
+ | `randomBytes(n)` | Cryptographically secure random bytes via Web Crypto |
224
+
225
+ ## Authentication Warning
226
+
227
+ `SerpentCtr` and `SerpentCbc` are **unauthenticated** cipher modes. They provide
228
+ confidentiality but not integrity or authenticity. An attacker can modify
229
+ ciphertext without detection.
230
+
231
+ >[!TIP]
232
+ > **For authenticated Serpent encryption:** use [`SerpentSeal`](https://github.com/xero/leviathan-crypto/wiki/serpent#serpentseal) or [`SerpentStreamSealer`](https://github.com/xero/leviathan-crypto/wiki/serpent#serpentstreamsealer--serpentstreamopener)
233
+ >
234
+ > **Using Serpent CBC/CTR directly:** pair with `HMAC_SHA256` using the Encrypt-then-MAC pattern
235
+
236
+ >[!NOTE]
237
+ > **[`SerpentStream`](https://github.com/xero/leviathan-crypto/wiki/serpent#serpentstream) and [`SerpentStreamSealer`](https://github.com/xero/leviathan-crypto/wiki/serpent#serpentstreamsealer--serpentstreamopener)
238
+ > inherently satisfy the Cryptographic Doom Principle.** Message Authentication Code (MAC)
239
+ > verification is the mandatory check on every `open()` call; decryption is impossible until
240
+ > this verification succeeds. Per-chunk HKDF key derivation, using position-bound info,
241
+ > extends this protection to stream integrity. Reordering, truncation, and cross-stream
242
+ > substitution are all detected at the MAC layer, preventing any plaintext from being
243
+ > produced in such cases. [Full analysis.](https://github.com/xero/leviathan-crypto/wiki/serpent_audit#24-serpentstream-encrypt-then-mac-and-the-cryptographic-doom-principle)
244
+
245
+ ## Installation
246
+
247
+ ```bash
248
+ # use bun
249
+ bun i leviathan-crypto
250
+ # or npm
251
+ npm install leviathan-crypto
252
+ ```
253
+
254
+ ## Loading Modes
255
+
256
+ ```typescript
257
+ // Embedded (default): zero-config, base64-encoded WASM inline
258
+ await init(['serpent', 'sha3'])
259
+
260
+ // Streaming: uses instantiateStreaming for performance
261
+ await init(['serpent'], 'streaming', { wasmUrl: '/assets/wasm/' })
262
+
263
+ // Manual: provide your own binary
264
+ await init(['serpent'], 'manual', { wasmBinary: { serpent: myBuffer } })
265
+ ```
266
+
267
+ ## Documentation
268
+
269
+ **Full API documentation:** [./docs](./docs/README.md)
270
+
271
+ | Module | Description |
272
+ |--------|-------------|
273
+ | [serpent.md](./docs/serpent.md) | Serpent-256 TypeScript API (`SerpentSeal`, `SerpentStream`, `SerpentStreamPool`, `SerpentStreamSealer`, `SerpentStreamOpener`, `Serpent`, `SerpentCtr`, `SerpentCbc`) |
274
+ | [asm_serpent.md](./docs/asm_serpent.md) | Serpent-256 WASM implementation (bitslice S-boxes, key schedule, CTR/CBC) |
275
+ | [chacha20.md](./docs/chacha20.md) | ChaCha20/Poly1305 TypeScript API (`ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`) |
276
+ | [asm_chacha.md](./docs/asm_chacha.md) | ChaCha20/Poly1305 WASM implementation (quarter-round, HChaCha20) |
277
+ | [sha2.md](./docs/sha2.md) | SHA-2 TypeScript API (`SHA256`, `SHA512`, `SHA384`, `HMAC_SHA256`, `HMAC_SHA512`, `HMAC_SHA384`) |
278
+ | [asm_sha2.md](./docs/asm_sha2.md) | SHA-2 WASM implementation (compression functions, HMAC) |
279
+ | [sha3.md](./docs/sha3.md) | SHA-3 TypeScript API (`SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256`) |
280
+ | [asm_sha3.md](./docs/asm_sha3.md) | SHA-3 WASM implementation (Keccak-f[1600], sponge construction) |
281
+ | [fortuna.md](./docs/fortuna.md) | Fortuna CSPRNG (forward secrecy, 32 entropy pools) |
282
+ | [init.md](./docs/init.md) | `init()` API and WASM loading modes |
283
+ | [utils.md](./docs/utils.md) | Encoding helpers, `constantTimeEqual`, `wipe`, `randomBytes` |
284
+ | [types.md](./docs/types.md) | TypeScript interfaces (`Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD`) |
285
+ | [architecture.md](./docs/architecture.md.md) | Architecture overview, build pipeline, module relationships |
286
+ | [test-suite.md](./test-suite.md) | Test suite structure, vector corpus, gate discipline |
287
+
288
+ ## License
289
+ leviathan is written under the [MIT license](http://www.opensource.org/licenses/MIT).
290
+
291
+ ```
292
+ ██ ▐█████ ██ ▐█▌ ▄█▌ ███▌ ▀███████▀▄██▌ ▐█▌ ███▌ ██▌ ▓▓
293
+ ▐█▌ ▐█▌ ▓█ ▐█▌ ▓██ ▐█▌██ ▐█▌ ███ ██▌ ▐█▌██ ▓██ ██
294
+ ██▌ ░███ ▐█▌ ██ ▀▀ ██ ▐█▌ ██ ▐██▌ █▓ ▓█ ▐█▌ ▐███▌ █▓
295
+ ██ ██ ▐█▌ █▓ ▐██ ▐█▌ █▓ ██ ▐██▄▄ ▐█▌ ▐█▌ ██ ▐█▌██ ▐█▌
296
+ ▐█▌ ▐█▌ ██ ▐█▌ ██ ██ ██ ▐█▌ ██▀▀████▌ ██ ██ ██ ▐█▌▐█▌
297
+ ▐▒▌ ▐▒▌ ▐▒▌ ██ ▒█ ██▀▀▀██▌ ▐▒▌ ▒█ █▓░ ▒█▀▀▀██▌ ▒█ ██▐█
298
+ █▓ ▄▄▓█ █▓ ▄▄▓█ ▓▓ ▐▓▌ ▐▓▌ ▐█▌ ▐▒▌ █▓ ▐▓▌ ▐▓█ ▐▓▌ ▐▒▌▐▓▌ ▐███
299
+ ▓██▀▀ ▓██▀▀ ▓█▓█ ▐█▌ ▐█▌ ▐▓▌ ▓█ ▐█▌ ▐█▓ ▐█▌ ▐▓▌▐█▌ ██▓
300
+ ▓█ ▄▄▄▄▄▄▄▄▄▄ ▀▀ ▐█▌▌▌
301
+ ▄████████████████████▄▄
302
+ ▄██████████████████████ ▀████▄
303
+ ▄█████████▀▀▀ ▀███████▄▄███████▌
304
+ ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
305
+ ████████ ███▀▀ ████▀ █▀ █▀
306
+ ███████▌ ▀██▀ ██
307
+ ███████ ▀███ ▀██ ▀█▄
308
+ ▀██████ ▄▄██ ▀▀ ██▄
309
+ ▀█████▄ ▄██▄ ▄▀▄▀
310
+ ▀████▄ ▄██▄
311
+ ▐████ ▐███
312
+ ▄▄██████████ ▐███ ▄▄
313
+ ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
314
+ ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███
315
+ ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀
316
+ ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄
317
+ █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄███▀
318
+ ▀██████▀ ▀████▄▄▄████▀
319
+ ▀█████▀
320
+
321
+ Serpent256 & Xchacha20-Poly1305 Cryptography for the Web
322
+ ```