leviathan-crypto 1.4.0 → 2.0.1

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 (122) hide show
  1. package/CLAUDE.md +129 -94
  2. package/README.md +166 -223
  3. package/SECURITY.md +90 -45
  4. package/dist/chacha20/cipher-suite.d.ts +4 -0
  5. package/dist/chacha20/cipher-suite.js +79 -0
  6. package/dist/chacha20/embedded.d.ts +1 -0
  7. package/dist/chacha20/embedded.js +27 -0
  8. package/dist/chacha20/index.d.ts +20 -27
  9. package/dist/chacha20/index.js +40 -59
  10. package/dist/chacha20/ops.d.ts +1 -1
  11. package/dist/chacha20/ops.js +19 -18
  12. package/dist/chacha20/pool-worker.js +77 -0
  13. package/dist/ct-wasm.d.ts +1 -0
  14. package/dist/ct-wasm.js +3 -0
  15. package/dist/ct.wasm +0 -0
  16. package/dist/docs/aead.md +323 -0
  17. package/dist/docs/architecture.md +427 -292
  18. package/dist/docs/argon2id.md +42 -30
  19. package/dist/docs/chacha20.md +192 -266
  20. package/dist/docs/exports.md +241 -0
  21. package/dist/docs/fortuna.md +60 -69
  22. package/dist/docs/init.md +172 -178
  23. package/dist/docs/loader.md +87 -142
  24. package/dist/docs/serpent.md +134 -583
  25. package/dist/docs/sha2.md +91 -103
  26. package/dist/docs/sha3.md +70 -36
  27. package/dist/docs/types.md +94 -16
  28. package/dist/docs/utils.md +109 -32
  29. package/dist/embedded/kyber.d.ts +1 -0
  30. package/dist/embedded/kyber.js +3 -0
  31. package/dist/embedded/serpent.d.ts +1 -1
  32. package/dist/embedded/serpent.js +1 -1
  33. package/dist/errors.d.ts +10 -0
  34. package/dist/errors.js +38 -0
  35. package/dist/fortuna.d.ts +0 -6
  36. package/dist/fortuna.js +5 -5
  37. package/dist/index.d.ts +25 -9
  38. package/dist/index.js +36 -7
  39. package/dist/init.d.ts +3 -7
  40. package/dist/init.js +18 -35
  41. package/dist/keccak/embedded.d.ts +1 -0
  42. package/dist/keccak/embedded.js +27 -0
  43. package/dist/keccak/index.d.ts +4 -0
  44. package/dist/keccak/index.js +31 -0
  45. package/dist/kyber/embedded.d.ts +1 -0
  46. package/dist/kyber/embedded.js +27 -0
  47. package/dist/kyber/indcpa.d.ts +49 -0
  48. package/dist/kyber/indcpa.js +352 -0
  49. package/dist/kyber/index.d.ts +38 -0
  50. package/dist/kyber/index.js +150 -0
  51. package/dist/kyber/kem.d.ts +21 -0
  52. package/dist/kyber/kem.js +160 -0
  53. package/dist/kyber/params.d.ts +14 -0
  54. package/dist/kyber/params.js +37 -0
  55. package/dist/kyber/suite.d.ts +13 -0
  56. package/dist/kyber/suite.js +94 -0
  57. package/dist/kyber/types.d.ts +98 -0
  58. package/dist/kyber/types.js +25 -0
  59. package/dist/kyber/validate.d.ts +19 -0
  60. package/dist/kyber/validate.js +68 -0
  61. package/dist/kyber.wasm +0 -0
  62. package/dist/loader.d.ts +15 -6
  63. package/dist/loader.js +65 -21
  64. package/dist/serpent/cipher-suite.d.ts +4 -0
  65. package/dist/serpent/cipher-suite.js +122 -0
  66. package/dist/serpent/embedded.d.ts +1 -0
  67. package/dist/serpent/embedded.js +27 -0
  68. package/dist/serpent/index.d.ts +6 -37
  69. package/dist/serpent/index.js +9 -118
  70. package/dist/serpent/pool-worker.d.ts +1 -0
  71. package/dist/serpent/pool-worker.js +208 -0
  72. package/dist/serpent/serpent-cbc.d.ts +30 -0
  73. package/dist/serpent/serpent-cbc.js +142 -0
  74. package/dist/serpent.wasm +0 -0
  75. package/dist/sha2/embedded.d.ts +1 -0
  76. package/dist/sha2/embedded.js +27 -0
  77. package/dist/sha2/hkdf.js +6 -2
  78. package/dist/sha2/index.d.ts +3 -2
  79. package/dist/sha2/index.js +3 -4
  80. package/dist/sha3/embedded.d.ts +1 -0
  81. package/dist/sha3/embedded.js +27 -0
  82. package/dist/sha3/index.d.ts +3 -2
  83. package/dist/sha3/index.js +3 -4
  84. package/dist/stream/constants.d.ts +6 -0
  85. package/dist/stream/constants.js +30 -0
  86. package/dist/stream/header.d.ts +9 -0
  87. package/dist/stream/header.js +77 -0
  88. package/dist/stream/index.d.ts +7 -0
  89. package/dist/stream/index.js +27 -0
  90. package/dist/stream/open-stream.d.ts +21 -0
  91. package/dist/stream/open-stream.js +146 -0
  92. package/dist/stream/seal-stream-pool.d.ts +38 -0
  93. package/dist/stream/seal-stream-pool.js +400 -0
  94. package/dist/stream/seal-stream.d.ts +20 -0
  95. package/dist/stream/seal-stream.js +142 -0
  96. package/dist/stream/seal.d.ts +9 -0
  97. package/dist/stream/seal.js +75 -0
  98. package/dist/stream/types.d.ts +25 -0
  99. package/dist/stream/types.js +26 -0
  100. package/dist/utils.d.ts +7 -2
  101. package/dist/utils.js +49 -3
  102. package/dist/wasm-source.d.ts +12 -0
  103. package/dist/wasm-source.js +26 -0
  104. package/package.json +13 -5
  105. package/dist/chacha20/pool.d.ts +0 -52
  106. package/dist/chacha20/pool.js +0 -178
  107. package/dist/chacha20/pool.worker.js +0 -37
  108. package/dist/chacha20/stream-sealer.d.ts +0 -49
  109. package/dist/chacha20/stream-sealer.js +0 -327
  110. package/dist/docs/chacha20_pool.md +0 -309
  111. package/dist/docs/wasm.md +0 -194
  112. package/dist/serpent/seal.d.ts +0 -8
  113. package/dist/serpent/seal.js +0 -72
  114. package/dist/serpent/stream-pool.d.ts +0 -48
  115. package/dist/serpent/stream-pool.js +0 -275
  116. package/dist/serpent/stream-sealer.d.ts +0 -55
  117. package/dist/serpent/stream-sealer.js +0 -342
  118. package/dist/serpent/stream.d.ts +0 -28
  119. package/dist/serpent/stream.js +0 -205
  120. package/dist/serpent/stream.worker.d.ts +0 -32
  121. package/dist/serpent/stream.worker.js +0 -117
  122. /package/dist/chacha20/{pool.worker.d.ts → pool-worker.d.ts} +0 -0
@@ -1,24 +1,31 @@
1
- # Serpent-256 block cipher TypeScript API
1
+ # Serpent-256 TypeScript API
2
2
 
3
3
  > [!NOTE]
4
- > Authenticated encryption via `SerpentSeal`, plus low-level block, CTR, and CBC
5
- > classes for advanced use.
6
- >
7
4
  > See [Serpent implementation audit](./serpent_audit.md) for algorithm correctness verifications.
8
5
 
6
+ > ### Table of Contents
7
+ > - [Overview](#overview)
8
+ > - [Security Notes](#security-notes)
9
+ > - [Module Init](#module-init)
10
+ > - [API Reference](#api-reference)
11
+ > - [Usage Examples](#usage-examples)
12
+ > - [Error Conditions](#error-conditions)
13
+
14
+ ---
15
+
9
16
  ## Overview
10
17
 
11
- `SerpentSeal` is the primary encryption API for the Serpent module. It provides
12
- authenticated Serpent-256 encryption in a single call -- no manual IV generation,
13
- no separate MAC step, no room for misuse. Internally it uses Encrypt-then-MAC
14
- (SerpentCbc + HMAC-SHA256) and verifies authentication before decryption.
18
+ `SerpentCipher` is the primary API for authenticated Serpent-256 encryption. Pass it
19
+ to `Seal` for one-shot AEAD, or to `SealStream`/`OpenStream` for streaming. There is
20
+ no manual IV generation, no separate MAC step, and no room for misuse. Internally it
21
+ uses Encrypt-then-MAC (Serpent-CBC + HMAC-SHA-256) with HKDF key derivation.
15
22
 
16
23
  For advanced use cases, three lower-level classes are available: `Serpent` (raw
17
24
  16-byte block operations), `SerpentCtr` (counter mode streaming), and `SerpentCbc`
18
25
  (cipher block chaining with PKCS7 padding). These are unauthenticated and require
19
26
  explicit opt-in.
20
27
 
21
- Serpent was an AES finalist. It uses 32 rounds versus AES's 10--14, yielding a
28
+ Serpent was an AES finalist. It uses 32 rounds versus AES's 10 to 14, yielding a
22
29
  larger security margin at comparable speed in WASM.
23
30
 
24
31
  ---
@@ -33,35 +40,31 @@ larger security margin at comparable speed in WASM.
33
40
 
34
41
  This is the most dangerous mistake you can make with this module. An attacker who
35
42
  can modify ciphertext encrypted with `SerpentCbc` or `SerpentCtr` will produce
36
- corrupted plaintext on decryption -- and decryption will succeed without any
37
- indication of tampering. There is no integrity check. The caller receives garbage
38
- and has no way to distinguish it from the original message.
43
+ corrupted plaintext on decryption. Decryption succeeds without any indication of
44
+ tampering. There is no integrity check. Your caller receives garbage and has no way
45
+ to distinguish it from the original message.
39
46
 
40
- `SerpentSeal` eliminates this problem. It computes an HMAC tag over the ciphertext
41
- and verifies it before decryption. If anything has been modified, `decrypt()` throws
42
- instead of returning corrupted data.
47
+ `Seal` with `SerpentCipher` eliminates this problem. It computes an HMAC tag over
48
+ the ciphertext and verifies it before decryption. If anything has been modified,
49
+ `Seal.decrypt()` throws instead of returning corrupted data.
43
50
 
44
- Leviathan also offers a `XChaCha20Poly1305` implementation an alternative that
45
- provides authenticated encryption with a different cipher. See
46
- [chacha20.md](./chacha20.md).
51
+ `Seal` with `XChaCha20Cipher` is an alternative using a different cipher.
52
+ See [chacha20.md](./chacha20.md).
47
53
 
48
54
  ### Never reuse a nonce or IV with the same key
49
55
 
50
- - **CTR mode**: Reusing a nonce with the same key is catastrophic. It produces the
51
- same keystream, which means an attacker can XOR two ciphertexts together and
52
- recover both plaintexts. Always generate a fresh random nonce for each message.
53
- - **CBC mode**: The IV (initialization vector) must be random and unpredictable for
54
- each encryption. A predictable IV enables chosen-plaintext attacks.
56
+ In CTR mode, reusing a nonce with the same key is catastrophic. It produces the
57
+ same keystream, which means an attacker can XOR two ciphertexts together and
58
+ recover both plaintexts. Always generate a fresh random nonce for each message.
59
+ In CBC mode, the IV must be random and unpredictable for each encryption. A predictable IV enables chosen-plaintext attacks.
55
60
 
56
- Use `randomBytes(16)` to generate nonces and IVs. `SerpentSeal` handles IV
57
- generation internally.
61
+ Use `randomBytes(16)` to generate nonces and IVs. `Seal` with `SerpentCipher` handles IV generation internally.
58
62
 
59
63
  ### Always use 256-bit keys
60
64
 
61
65
  Unless you have a specific reason to use a shorter key, pass a 32-byte key to
62
66
  every Serpent operation. Shorter keys provide less security margin and there is no
63
- meaningful performance benefit to using them. `SerpentSeal` requires a 64-byte key
64
- (32 bytes encryption + 32 bytes MAC).
67
+ meaningful performance benefit to using them. `SerpentCipher` requires a 32-byte key; HKDF derives enc/mac/iv keys internally.
65
68
 
66
69
  ### Call dispose() when done
67
70
 
@@ -77,24 +80,25 @@ in memory longer than necessary.
77
80
  Each module subpath exports its own init function for consumers who want
78
81
  tree-shakeable imports.
79
82
 
80
- ### `serpentInit(mode?, opts?)`
83
+ ### `serpentInit(source)`
81
84
 
82
85
  Initializes only the serpent WASM binary. Equivalent to calling the
83
- root `init(['serpent'], mode, opts)` but without pulling the other three
86
+ root `init({ serpent: serpentWasm })` but without pulling the other three
84
87
  modules into the bundle.
85
88
 
86
89
  **Signature:**
87
90
 
88
91
  ```typescript
89
- async function serpentInit(mode?: Mode, opts?: InitOpts): Promise<void>
92
+ async function serpentInit(source: WasmSource): Promise<void>
90
93
  ```
91
94
 
92
95
  **Usage:**
93
96
 
94
97
  ```typescript
95
98
  import { serpentInit, Serpent } from 'leviathan-crypto/serpent'
99
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
96
100
 
97
- await serpentInit()
101
+ await serpentInit(serpentWasm)
98
102
  const cipher = new Serpent()
99
103
  ```
100
104
 
@@ -103,73 +107,66 @@ const cipher = new Serpent()
103
107
  ## API Reference
104
108
 
105
109
  All classes require their WASM modules to be initialized before construction.
106
- `SerpentSeal` requires both `serpent` and `sha2`. All other classes require
107
- `serpent` only.
110
+ `SerpentCipher` (and therefore `Seal`, `SealStream`, `OpenStream`) requires both
111
+ `serpent` and `sha2`. `Serpent`, `SerpentCtr`, and `SerpentCbc` require `serpent` only.
108
112
 
109
- ### SerpentSeal
113
+ ### SerpentCipher
110
114
 
111
- Authenticated Serpent-256 encryption. Handles IV generation, HMAC computation,
112
- and verification internally -- no manual IV or MAC management required.
115
+ `CipherSuite` implementation for Serpent-256 CBC+HMAC-SHA-256. Pass to `Seal`,
116
+ `SealStream`, or `OpenStream`. Never instantiated directly.
113
117
 
114
- ```typescript
115
- class SerpentSeal {
116
- constructor()
117
- encrypt(key: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Uint8Array
118
- decrypt(key: Uint8Array, data: Uint8Array, aad?: Uint8Array): Uint8Array
119
- dispose(): void
120
- }
121
- ```
122
-
123
- #### `constructor()`
124
-
125
- Creates a new SerpentSeal instance. Throws if `init(['serpent', 'sha2'])` has not
126
- been called.
118
+ Requires `init({ serpent: serpentWasm, sha2: sha2Wasm })`.
127
119
 
128
- ---
120
+ | Property | Value |
121
+ |----------|-------|
122
+ | `formatEnum` | `0x02` |
123
+ | `keySize` | `32` |
124
+ | `tagSize` | `32` (HMAC-SHA-256) |
125
+ | `padded` | `true` (PKCS7) |
126
+ | `wasmModules` | `['serpent', 'sha2']` |
129
127
 
130
- #### `encrypt(key: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Uint8Array`
128
+ #### `SerpentCipher.keygen(): Uint8Array`
131
129
 
132
- Encrypts plaintext and returns a sealed blob containing the ciphertext and
133
- authentication data. The output is opaque -- pass it directly to `decrypt()`.
130
+ Returns `randomBytes(32)`. Convenience method. Not on the `CipherSuite` interface.
134
131
 
135
- - **key** -- exactly 64 bytes (32 bytes encryption key + 32 bytes MAC key).
136
- Throws `RangeError` if the length is not 64.
137
- - **plaintext** -- any length.
138
- - **aad** -- optional associated data. Authenticated but not encrypted. Bound
139
- into the HMAC-SHA256 tag. If omitted, equivalent to empty.
132
+ #### Usage with `Seal`
140
133
 
141
- A fresh random IV is generated internally for each call. Two encryptions of the
142
- same plaintext with the same key produce different output.
134
+ ```typescript
135
+ import { init, Seal, SerpentCipher } from 'leviathan-crypto'
136
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
137
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
143
138
 
144
- ---
139
+ await init({ serpent: serpentWasm, sha2: sha2Wasm })
145
140
 
146
- #### `decrypt(key: Uint8Array, data: Uint8Array, aad?: Uint8Array): Uint8Array`
141
+ const key = SerpentCipher.keygen()
142
+ const blob = Seal.encrypt(SerpentCipher, key, plaintext)
143
+ const pt = Seal.decrypt(SerpentCipher, key, blob) // throws on tamper
144
+ ```
147
145
 
148
- Verifies the authentication tag and decrypts the sealed blob. MAC verification
149
- happens before decryption -- if the data has been tampered with, `decrypt()` throws
150
- and never returns corrupted plaintext.
146
+ #### Usage with `SealStream` / `OpenStream`
151
147
 
152
- - **key** -- exactly 64 bytes. Must be the same key used for encryption. Throws
153
- `RangeError` if the length is not 64.
154
- - **data** -- the sealed blob from `encrypt()`. Must be at least 64 bytes. Throws
155
- `RangeError` if shorter. Throws `Error` if authentication fails.
156
- - **aad** -- optional associated data. If `aad` was passed to `encrypt()`, the
157
- same value must be passed to `decrypt()`. Mismatch causes authentication failure.
148
+ ```typescript
149
+ import { SealStream, OpenStream } from 'leviathan-crypto/stream'
150
+ import { SerpentCipher } from 'leviathan-crypto/serpent'
158
151
 
159
- ---
152
+ const sealer = new SealStream(SerpentCipher, key)
153
+ const preamble = sealer.preamble // 20 bytes, send before first chunk
154
+ const ct0 = sealer.push(chunk0)
155
+ const ctLast = sealer.finalize(lastChunk)
160
156
 
161
- #### `dispose(): void`
157
+ const opener = new OpenStream(SerpentCipher, key, preamble)
158
+ const pt0 = opener.pull(ct0)
159
+ const ptLast = opener.finalize(ctLast)
160
+ ```
162
161
 
163
- Wipes all key material and intermediate state from WASM memory. Delegates to both
164
- internal SerpentCbc and HMAC_SHA256 instances.
162
+ See [aead.md](./aead.md) for the full `Seal`, `SealStream`, and `OpenStream` API.
165
163
 
166
164
  ---
167
165
 
168
166
  ### Serpent
169
167
 
170
168
  Raw Serpent block encryption and decryption. Operates on exactly 16-byte blocks.
171
- This class is a low-level building block, most users should use `SerpentSeal`
172
- instead.
169
+ This class is a low-level building block; use `Seal` with `SerpentCipher` for most purposes.
173
170
 
174
171
  ```typescript
175
172
  class Serpent {
@@ -183,7 +180,7 @@ class Serpent {
183
180
 
184
181
  #### `constructor()`
185
182
 
186
- Creates a new Serpent instance. Throws if `init(['serpent'])` has not been called.
183
+ Creates a new Serpent instance. Throws if `init({ serpent: serpentWasm })` has not been called.
187
184
 
188
185
  ---
189
186
 
@@ -192,7 +189,7 @@ Creates a new Serpent instance. Throws if `init(['serpent'])` has not been calle
192
189
  Loads and expands a key for subsequent block operations. Must be called before
193
190
  `encryptBlock()` or `decryptBlock()`.
194
191
 
195
- - **key** -- 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
192
+ - **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
196
193
 
197
194
  ---
198
195
 
@@ -200,7 +197,7 @@ Loads and expands a key for subsequent block operations. Must be called before
200
197
 
201
198
  Encrypts a single 16-byte block and returns the 16-byte ciphertext.
202
199
 
203
- - **plaintext** -- exactly 16 bytes. Throws `RangeError` if the length is not 16.
200
+ - **plaintext**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
204
201
 
205
202
  ---
206
203
 
@@ -208,7 +205,7 @@ Encrypts a single 16-byte block and returns the 16-byte ciphertext.
208
205
 
209
206
  Decrypts a single 16-byte block and returns the 16-byte plaintext.
210
207
 
211
- - **ciphertext** -- exactly 16 bytes. Throws `RangeError` if the length is not 16.
208
+ - **ciphertext**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
212
209
 
213
210
  ---
214
211
 
@@ -226,7 +223,7 @@ stream of chunks.
226
223
 
227
224
  > [!WARNING]
228
225
  > CTR mode is unauthenticated. An attacker can modify ciphertext
229
- > without detection. Use `SerpentSeal` for authenticated encryption, or pair
226
+ > without detection. Use `Seal` with `SerpentCipher` for authenticated encryption, or pair
230
227
  > with HMAC-SHA256 (Encrypt-then-MAC).
231
228
 
232
229
  ```typescript
@@ -242,11 +239,11 @@ class SerpentCtr {
242
239
 
243
240
  #### `constructor(opts: { dangerUnauthenticated: true })`
244
241
 
245
- Creates a new SerpentCtr instance. Throws if `init(['serpent'])` has not been
242
+ Creates a new SerpentCtr instance. Throws if `init({ serpent: serpentWasm })` has not been
246
243
  called. Throws if `{ dangerUnauthenticated: true }` is not passed:
247
244
 
248
245
  ```
249
- leviathan-crypto: SerpentCtr is unauthenticated — use SerpentSeal instead.
246
+ leviathan-crypto: SerpentCtr is unauthenticated — use Seal with SerpentCipher instead.
250
247
  To use SerpentCtr directly, pass { dangerUnauthenticated: true }.
251
248
  ```
252
249
 
@@ -257,8 +254,8 @@ To use SerpentCtr directly, pass { dangerUnauthenticated: true }.
257
254
  Initializes the CTR state for encryption. Loads the key, sets the nonce, and
258
255
  resets the internal counter to zero.
259
256
 
260
- - **key** -- 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
261
- - **nonce** -- exactly 16 bytes. Throws `RangeError` if the length is not 16.
257
+ - **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
258
+ - **nonce**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
262
259
 
263
260
  ---
264
261
 
@@ -268,24 +265,19 @@ Encrypts a chunk of plaintext and returns the same-length ciphertext. Call this
268
265
  one or more times after `beginEncrypt()`. The internal counter advances
269
266
  automatically.
270
267
 
271
- - **chunk** -- any length up to the module's internal chunk buffer size. Throws
272
- `RangeError` if the chunk exceeds the maximum size.
268
+ - **chunk**: any length up to the module's internal chunk buffer size. Throws `RangeError` if the chunk exceeds the maximum size.
273
269
 
274
270
  > [!NOTE]
275
- > Automatically dispatches to the 4-wide SIMD path (`encryptChunk_simd`) when
276
- > the runtime supports WebAssembly SIMD (`hasSIMD()` returns `true`), otherwise
277
- > falls back to the scalar unrolled path. The dispatch is transparent — no API
278
- > change required.
271
+ > Automatically dispatches to the 4-wide SIMD path (`encryptChunk_simd`) when the runtime supports WebAssembly SIMD (`hasSIMD()` returns `true`), otherwise falls back to the scalar unrolled path. The dispatch is transparent with no API change required.
279
272
 
280
273
  ---
281
274
 
282
275
  #### `beginDecrypt(key: Uint8Array, nonce: Uint8Array): void`
283
276
 
284
- Initializes the CTR state for decryption. Functionally identical to
285
- `beginEncrypt()` -- CTR mode uses the same operation in both directions.
277
+ Initializes the CTR state for decryption. Functionally identical to `beginEncrypt()`. CTR mode uses the same operation in both directions.
286
278
 
287
- - **key** -- 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
288
- - **nonce** -- exactly 16 bytes. Throws `RangeError` if the length is not 16.
279
+ - **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
280
+ - **nonce**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
289
281
 
290
282
  ---
291
283
 
@@ -294,8 +286,7 @@ Initializes the CTR state for decryption. Functionally identical to
294
286
  Decrypts a chunk of ciphertext and returns the same-length plaintext.
295
287
  Functionally identical to `encryptChunk()`.
296
288
 
297
- - **chunk** -- any length up to the module's internal chunk buffer size. Throws
298
- `RangeError` if the chunk exceeds the maximum size.
289
+ - **chunk**: any length up to the module's internal chunk buffer size. Throws `RangeError` if the chunk exceeds the maximum size.
299
290
 
300
291
  ---
301
292
 
@@ -312,7 +303,7 @@ Encrypts and decrypts entire messages in a single call.
312
303
 
313
304
  > [!WARNING]
314
305
  > CBC mode is unauthenticated. Always authenticate the output with
315
- > HMAC-SHA256 (Encrypt-then-MAC) or use `SerpentSeal` instead.
306
+ > HMAC-SHA256 (Encrypt-then-MAC) or use `Seal` with `SerpentCipher` instead.
316
307
 
317
308
  ```typescript
318
309
  class SerpentCbc {
@@ -325,11 +316,11 @@ class SerpentCbc {
325
316
 
326
317
  #### `constructor(opts: { dangerUnauthenticated: true })`
327
318
 
328
- Creates a new SerpentCbc instance. Throws if `init(['serpent'])` has not been
319
+ Creates a new SerpentCbc instance. Throws if `init({ serpent: serpentWasm })` has not been
329
320
  called. Throws if `{ dangerUnauthenticated: true }` is not passed:
330
321
 
331
322
  ```
332
- leviathan-crypto: SerpentCbc is unauthenticated — use SerpentSeal instead.
323
+ leviathan-crypto: SerpentCbc is unauthenticated — use Seal with SerpentCipher instead.
333
324
  To use SerpentCbc directly, pass { dangerUnauthenticated: true }.
334
325
  ```
335
326
 
@@ -341,11 +332,9 @@ Encrypts plaintext with Serpent CBC and PKCS7 padding. The returned ciphertext i
341
332
  always a multiple of 16 bytes and is at least 16 bytes longer than the input (due
342
333
  to padding).
343
334
 
344
- - **key** -- 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
345
- - **iv** -- exactly 16 bytes. Must be random and unique for each (key, message)
346
- pair. Throws `RangeError` if the length is not 16.
347
- - **plaintext** -- any length (including zero). PKCS7 padding is applied
348
- automatically.
335
+ - **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
336
+ - **iv**: exactly 16 bytes. Must be random and unique per (key, message) pair. Throws `RangeError` if the length is not 16.
337
+ - **plaintext**: any length including zero. PKCS7 padding is applied automatically.
349
338
 
350
339
  Returns the ciphertext as a new `Uint8Array`.
351
340
 
@@ -355,21 +344,14 @@ Returns the ciphertext as a new `Uint8Array`.
355
344
 
356
345
  Decrypts Serpent CBC ciphertext and strips PKCS7 padding.
357
346
 
358
- - **key** -- 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
359
- - **iv** -- exactly 16 bytes. Must be the same IV that was used for encryption.
360
- Throws `RangeError` if the length is not 16.
361
- - **ciphertext** -- must be a non-zero multiple of 16 bytes. Throws `RangeError`
362
- if the length is zero or not a multiple of 16. Also throws `RangeError` if PKCS7
363
- padding is invalid (which typically indicates the wrong key, wrong IV, or
364
- corrupted ciphertext).
347
+ - **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
348
+ - **iv**: exactly 16 bytes. Must match the IV used for encryption. Throws `RangeError` if the length is not 16.
349
+ - **ciphertext**: must be a non-zero multiple of 16 bytes. Throws `RangeError` if the length is zero or not a multiple of 16. Also throws if PKCS7 padding is invalid, which typically indicates the wrong key, wrong IV, or corrupted ciphertext.
365
350
 
366
351
  Returns the decrypted plaintext as a new `Uint8Array`.
367
352
 
368
353
  > [!NOTE]
369
- > Automatically dispatches to the 4-wide SIMD path (`cbcDecryptChunk_simd`) when
370
- > the runtime supports WebAssembly SIMD (`hasSIMD()` returns `true`), otherwise
371
- > falls back to the scalar unrolled path. CBC encryption has no SIMD variant —
372
- > each ciphertext block depends on the previous one.
354
+ > Automatically dispatches to the 4-wide SIMD path (`cbcDecryptChunk_simd`) when the runtime supports WebAssembly SIMD (`hasSIMD()` returns `true`), otherwise falls back to the scalar unrolled path. CBC encryption has no SIMD variant since each ciphertext block depends on the previous one.
373
355
 
374
356
  ---
375
357
 
@@ -379,357 +361,40 @@ Wipes all key material and intermediate state from WASM memory.
379
361
 
380
362
  ---
381
363
 
382
- ### SerpentStream
383
-
384
- Chunked authenticated encryption for large payloads. Each chunk is independently
385
- encrypted with Serpent-CTR and authenticated with HMAC-SHA256 using per-chunk
386
- keys derived via HKDF-SHA256. Position binding and truncation detection are
387
- enforced at the key-derivation layer.
388
-
389
- Use `SerpentStream` when the payload is large or when holding the entire
390
- plaintext in memory is undesirable. For small/medium payloads where a single
391
- `encrypt()`/`decrypt()` call is sufficient, use `SerpentSeal` instead.
392
-
393
- > [!NOTE]
394
- > `SerpentStream` takes a 32-byte key (HKDF handles expansion internally).
395
- > This differs from `SerpentSeal`, which takes 64 bytes.
396
-
397
- ```typescript
398
- class SerpentStream {
399
- constructor()
400
- seal(key: Uint8Array, plaintext: Uint8Array, chunkSize?: number): Uint8Array
401
- open(key: Uint8Array, ciphertext: Uint8Array): Uint8Array
402
- dispose(): void
403
- }
404
- ```
405
-
406
- #### `constructor()`
407
-
408
- Creates a new SerpentStream instance. Throws if `init(['serpent', 'sha2'])` has
409
- not been called.
410
-
411
- ---
412
-
413
- #### `seal(key: Uint8Array, plaintext: Uint8Array, chunkSize?: number): Uint8Array`
414
-
415
- Encrypts plaintext into a chunked authenticated wire format.
416
-
417
- - **key** -- exactly 32 bytes. Throws `RangeError` if not.
418
- - **plaintext** -- any length (including zero).
419
- - **chunkSize** -- optional, default 64KB. Valid range: 1KB to 64KB. Throws
420
- `RangeError` if outside range.
421
-
422
- A fresh random stream nonce is generated internally for each call. Two seals of
423
- the same plaintext with the same key produce different output.
424
-
425
- Wire format: `stream_nonce (16) || chunk_size (4, u32_be) || chunk_count (8, u64_be) || chunk_0 || ... || chunk_N-1`
426
-
427
- Each chunk on the wire: `ciphertext || hmac_tag (32 bytes)`.
428
-
429
- ---
430
-
431
- #### `open(key: Uint8Array, ciphertext: Uint8Array): Uint8Array`
432
-
433
- Verifies authentication and decrypts the chunked wire format. Each chunk's MAC
434
- is verified before decryption (Encrypt-then-MAC). If any chunk fails
435
- authentication, `open()` throws immediately and never returns partial plaintext.
436
-
437
- - **key** -- exactly 32 bytes. Must be the same key used for `seal()`.
438
- - **ciphertext** -- the wire format from `seal()`. Throws `RangeError` if too
439
- short.
440
-
441
- ---
442
-
443
- #### `dispose(): void`
444
-
445
- Wipes all key material and intermediate state from WASM memory. Delegates to
446
- internal SerpentCtr, HMAC_SHA256, and HKDF_SHA256 instances.
447
-
448
- **Security properties:**
449
-
450
- - **Per-chunk EtM** -- HMAC-SHA256 over ciphertext, verified before decrypt.
451
- - **Position binding** -- chunk index encoded in HKDF `info`. Reordering chunks
452
- produces wrong keys; MAC fails.
453
- - **Truncation detection** -- final chunk derives different keys than any
454
- intermediate chunk at the same index.
455
- - **Implicit header integrity** -- HKDF `info` embeds the full header. Tampering
456
- with any header field invalidates every chunk's MAC.
457
- - **Domain separation** -- `"serpent-stream-v1"` prefix prevents key confusion
458
- with SerpentSeal or other constructions.
459
-
460
- > [!IMPORTANT]
461
- > This is a bespoke construction (no external RFC). The compositional security
462
- > argument rests on HKDF (RFC 5869), HMAC-EtM, and Serpent-CTR. See
463
- > [sha2.md](./sha2.md) for HKDF details.
464
-
465
- > [!NOTE]
466
- > `sealChunk` and `openChunk` are exported from the serpent submodule for
467
- > internal use by the pool worker. They are not public API -- callers should use
468
- > `SerpentStream` or `SerpentStreamPool`.
469
-
470
- ---
471
-
472
- ### SerpentStreamPool
473
-
474
- Parallel worker pool for `SerpentStream`. Same wire format, same security
475
- properties, faster on multi-core hardware for large payloads. Each worker owns
476
- its own `serpent.wasm` and `sha2.wasm` instances with isolated linear memory.
477
-
478
- `SerpentStream.seal()` and `SerpentStreamPool.seal()` produce compatible wire
479
- formats -- either can decrypt the other's output.
480
-
481
- ```typescript
482
- class SerpentStreamPool {
483
- static async create(opts?: StreamPoolOpts): Promise<SerpentStreamPool>
484
- seal(key: Uint8Array, plaintext: Uint8Array, chunkSize?: number): Promise<Uint8Array>
485
- open(key: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array>
486
- dispose(): void
487
- get size(): number
488
- get queueDepth(): number
489
- }
490
- ```
491
-
492
- #### `static async create(opts?: StreamPoolOpts): Promise<SerpentStreamPool>`
493
-
494
- Creates a new pool. Requires `init(['serpent', 'sha2'])` to have been called.
495
- Compiles both WASM modules once and distributes them to all workers.
496
-
497
- - **opts.workers** -- number of workers to spawn. Default:
498
- `navigator.hardwareConcurrency ?? 4`.
499
-
500
- Uses a static factory pattern because worker initialization is async (WASM
501
- compilation and instantiation happen per worker).
502
-
503
- ---
504
-
505
- #### `seal(key, plaintext, chunkSize?)`
506
-
507
- Same parameters as `SerpentStream.seal()`, but returns a `Promise`. Key
508
- derivation happens on the main thread; chunk encryption is parallelised across
509
- workers.
510
-
511
- ---
512
-
513
- #### `open(key, ciphertext)`
514
-
515
- Same parameters as `SerpentStream.open()`, but returns a `Promise`. If any chunk
516
- fails authentication, the promise rejects immediately -- no partial plaintext is
517
- returned.
518
-
519
- ---
520
-
521
- #### `dispose()`
522
-
523
- Terminates all workers. Rejects all pending and queued jobs. Must be called to
524
- release worker resources when the pool is no longer needed.
525
-
526
- ---
527
-
528
- ### SerpentStreamSealer / SerpentStreamOpener
529
-
530
- Incremental streaming AEAD — seal and open one chunk at a time without holding
531
- the full message in memory. Unlike `SerpentStream` (which is one-shot),
532
- `SerpentStreamSealer` produces chunks as data arrives and `SerpentStreamOpener`
533
- authenticates and decrypts them individually.
534
-
535
- **Wire format (v1.4.0+):**
536
- ```
537
- header: nonce (16) || chunkSize_u32be (4) = 20 bytes
538
- chunk: isLast (1) || IV (16) || CBC_ciphertext (PKCS7-padded) || HMAC-SHA256 (32)
539
- ```
540
-
541
- > [!WARNING]
542
- > **Breaking change from v1.3.x:** Each chunk now prepends an explicit `isLast`
543
- > flag byte, and AAD is folded into the HMAC input with a 4-byte length prefix.
544
- > Ciphertexts produced by v1.3.x are not compatible with v1.4.0 openers, and
545
- > vice versa, even with empty AAD.
546
-
547
- Per-chunk keys are derived via HKDF-SHA256 from the stream key and a `chunkInfo`
548
- blob binding the stream nonce, chunk size, chunk index, and `isLast` flag. Each
549
- chunk is independently authenticated and position-bound — reordering, truncation,
550
- and cross-stream splicing are all detected.
551
-
552
- > [!NOTE]
553
- > `SerpentStreamSealer` requires a 64-byte key (same as `SerpentSeal`). HKDF
554
- > derives a fresh `encKey` + `macKey` pair for every chunk.
555
-
556
- > [!IMPORTANT]
557
- > The sealer produces a 20-byte header that **must** be transmitted to the opener
558
- > before any chunks. The opener is initialized with this header.
559
-
560
- ```typescript
561
- class SerpentStreamSealer {
562
- constructor(key: Uint8Array, chunkSize?: number, opts?: {
563
- framed?: boolean;
564
- aad?: Uint8Array; // authenticated per-chunk, not encrypted
565
- })
566
- header(): Uint8Array // call once before seal() — returns 20 bytes
567
- seal(plaintext: Uint8Array): Uint8Array // exactly chunkSize bytes
568
- final(plaintext: Uint8Array): Uint8Array // <= chunkSize bytes; wipes on return
569
- dispose(): void // abort mid-stream; wipes without final chunk
570
- }
571
-
572
- class SerpentStreamOpener {
573
- constructor(key: Uint8Array, header: Uint8Array, opts?: {
574
- framed?: boolean;
575
- aad?: Uint8Array; // must match value used by sealer
576
- })
577
- open(chunk: Uint8Array): Uint8Array // throws on auth failure or post-final
578
- feed(bytes: Uint8Array): Uint8Array[] // framed mode only — accumulates and parses frames
579
- dispose(): void
580
- }
581
- ```
582
-
583
- #### Sealer state machine
584
-
585
- | State | Valid calls |
586
- |---|---|
587
- | `fresh` | `header()`, `dispose()` |
588
- | `sealing` | `seal()`, `final()`, `dispose()` |
589
- | `dead` | `dispose()` (no-op) |
590
-
591
- `header()` transitions `fresh → sealing`. `final()` seals the last chunk, wipes
592
- all key material, and transitions to `dead`. `dispose()` wipes and transitions to
593
- `dead` from any state — use it to abort a stream before `final()` is called.
594
-
595
- Calling `header()` twice, `seal()` before `header()`, or any method after `final()`
596
- all throw immediately.
597
-
598
- ---
599
-
600
- #### Opener state machine
601
-
602
- The opener is ready as soon as it is constructed. It calls `open()` for each
603
- chunk in order. Once a chunk with `isLast` set passes authentication, the opener
604
- wipes its key material and transitions to `dead`. Subsequent `open()` calls throw.
605
-
606
- `dispose()` wipes and marks the instance dead from any state.
607
-
608
- ---
609
-
610
- #### `constructor(key, chunkSize?, opts?)`
611
-
612
- - **key** — 64-byte key. Throws `RangeError` if wrong length.
613
- - **chunkSize** — bytes per chunk. Must be 1024–65536. Default: 65536. Throws
614
- `RangeError` if out of range.
615
-
616
- ##### Options (`opts`)
617
-
618
- | Option | Type | Default | Description |
619
- |--------|------|---------|-------------|
620
- | `framed` | `boolean` | `false` | Prepend `u32be(sealedLen)` to each `seal()`/`final()` output. Use for flat byte streams (files, pipes, TCP). Omit when the transport already frames messages (WebSocket, IPC). |
621
- | `aad` | `Uint8Array` | `undefined` | Associated data — authenticated but not encrypted. Bound into each chunk's HMAC-SHA256 tag with a 4-byte length prefix for domain separation. If omitted, equivalent to empty. |
622
-
623
- ---
624
-
625
- #### `header()`
626
-
627
- Returns the 20-byte stream header (`nonce || u32be(chunkSize)`). Must be called
628
- once before the first `seal()`. Throws if called a second time or after `final()`.
629
-
630
- ---
631
-
632
- #### `seal(plaintext)`
633
-
634
- Seals one chunk. **Plaintext must be exactly `chunkSize` bytes.** Returns
635
- `IV (16) || ciphertext || HMAC (32)`. Throws `RangeError` if wrong size. Throws
636
- if called before `header()` or after `final()`.
637
-
638
- ---
639
-
640
- #### `final(plaintext)`
641
-
642
- Seals the last chunk. Plaintext may be 0–`chunkSize` bytes (partial chunk is
643
- valid). After producing output, wipes all key material and marks the sealer dead.
644
- Throws `RangeError` if plaintext exceeds `chunkSize`.
645
-
646
- ---
647
-
648
- #### `dispose()` (sealer)
649
-
650
- Aborts the stream. Wipes key material without producing a final chunk. The opener
651
- will see an incomplete stream and throw when it detects a missing final chunk.
652
- Safe to call after `final()` — no-op if already dead.
653
-
654
- ---
655
-
656
- #### `constructor(key, header, opts?)` (opener)
657
-
658
- - **key** — 64-byte key. Throws `RangeError` if wrong length.
659
- - **header** — 20-byte stream header from `sealer.header()`. Throws `RangeError`
660
- if wrong length.
661
-
662
- ##### Options (`opts`)
663
-
664
- | Option | Type | Default | Description |
665
- |--------|------|---------|-------------|
666
- | `framed` | `boolean` | `false` | Enable byte-accumulation mode. Parses `u32be` length prefixes and dispatches complete frames to `open()` internally. Required to use `feed()`. |
667
- | `aad` | `Uint8Array` | `undefined` | Associated data — must match the value used by the sealer. Mismatch causes authentication failure on `open()`. |
668
-
669
- ---
670
-
671
- #### `open(chunk)`
672
-
673
- Authenticates and decrypts one chunk. Throws `Error` on authentication failure.
674
- Throws `Error` if called after the final chunk has already been opened. Returns
675
- plaintext bytes (PKCS7 padding stripped).
676
-
677
- ---
678
-
679
- #### `feed(bytes: Uint8Array): Uint8Array[]`
680
-
681
- Only callable when constructed with `{ framed: true }`. Accumulates incoming bytes,
682
- parses `u32be` length prefixes, dispatches complete frames to `open()` internally.
683
- Returns an array of decrypted chunks — zero, one, or more per call depending on how
684
- many complete frames were buffered. Throws if called on an unframed opener.
685
-
686
- ---
687
-
688
- #### `dispose()` (opener)
689
-
690
- Wipes key material. Safe to call at any point — use to abort opening a stream
691
- early.
692
-
693
- ---
694
-
695
364
  ## Usage Examples
696
365
 
697
- ### Example 1: SerpentSeal (authenticated encryption)
366
+ ### Example 1: Seal with SerpentCipher (authenticated encryption)
698
367
 
699
368
  ```typescript
700
- import { init, SerpentSeal, randomBytes } from 'leviathan-crypto';
701
-
702
- await init(['serpent', 'sha2']);
369
+ import { init, Seal, SerpentCipher } from 'leviathan-crypto'
370
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
371
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
703
372
 
704
- // 64-byte key: 32 bytes encryption + 32 bytes MAC
705
- const key = randomBytes(64);
373
+ await init({ serpent: serpentWasm, sha2: sha2Wasm })
706
374
 
707
- const seal = new SerpentSeal();
375
+ const key = SerpentCipher.keygen()
376
+ const plaintext = new TextEncoder().encode('Authenticated secret message.')
377
+ const blob = Seal.encrypt(SerpentCipher, key, plaintext)
378
+ const decrypted = Seal.decrypt(SerpentCipher, key, blob)
708
379
 
709
- const plaintext = new TextEncoder().encode('Authenticated secret message.');
710
- const ciphertext = seal.encrypt(key, plaintext);
711
- const decrypted = seal.decrypt(key, ciphertext);
712
-
713
- console.log(new TextDecoder().decode(decrypted));
380
+ console.log(new TextDecoder().decode(decrypted))
714
381
  // "Authenticated secret message."
715
-
716
- seal.dispose();
717
382
  ```
718
383
 
719
384
  ### Example 2: CTR mode (advanced)
720
385
 
721
- Advanced use. For authenticated encryption, use `SerpentSeal`.
722
-
723
- Use `SerpentCtr` to encrypt data of any length. CTR mode produces ciphertext
724
- that is the same length as the plaintext -- no padding overhead.
386
+ For authenticated encryption, use `Seal` with `SerpentCipher`. Use `SerpentCtr` to
387
+ encrypt data of any length. CTR mode produces ciphertext the same length as the
388
+ plaintext with no padding overhead.
725
389
 
726
390
  ```typescript
727
391
  import { init, SerpentCtr, randomBytes } from 'leviathan-crypto';
392
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded';
728
393
 
729
- await init(['serpent']);
394
+ await init({ serpent: serpentWasm });
730
395
 
731
396
  const key = randomBytes(32); // 256-bit key
732
- const nonce = randomBytes(16); // 16-byte nonce -- NEVER reuse with the same key
397
+ const nonce = randomBytes(16); // 16-byte nonce, NEVER reuse with the same key
733
398
 
734
399
  const ctr = new SerpentCtr({ dangerUnauthenticated: true });
735
400
 
@@ -752,21 +417,21 @@ ctr.dispose();
752
417
 
753
418
  > [!IMPORTANT]
754
419
  > CTR mode is unauthenticated. An attacker can tamper with the
755
- > ciphertext without detection. Use `SerpentSeal` for authenticated encryption.
420
+ > ciphertext without detection. Use `Seal` with `SerpentCipher` for authenticated encryption.
756
421
 
757
422
  ### Example 3: CBC mode (advanced)
758
423
 
759
- Advanced use. For authenticated encryption, use `SerpentSeal`.
760
-
761
- Use `SerpentCbc` for message-level encryption with automatic PKCS7 padding.
424
+ For authenticated encryption, use `Seal` with `SerpentCipher`. Use `SerpentCbc` for
425
+ message-level encryption with automatic PKCS7 padding.
762
426
 
763
427
  ```typescript
764
428
  import { init, SerpentCbc, randomBytes } from 'leviathan-crypto';
429
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded';
765
430
 
766
- await init(['serpent']);
431
+ await init({ serpent: serpentWasm });
767
432
 
768
433
  const key = randomBytes(32); // 256-bit key
769
- const iv = randomBytes(16); // Random IV -- must be unique per message
434
+ const iv = randomBytes(16); // Random IV, must be unique per message
770
435
 
771
436
  const cbc = new SerpentCbc({ dangerUnauthenticated: true });
772
437
 
@@ -783,109 +448,18 @@ cbc.dispose();
783
448
  ```
784
449
 
785
450
  > [!IMPORTANT]
786
- > CBC mode is unauthenticated. Use `SerpentSeal` for authenticated encryption.
787
-
788
- ### Example 4: SerpentStream (chunked authenticated encryption)
789
-
790
- Use `SerpentStream` for large payloads where holding the entire plaintext in
791
- memory is undesirable.
792
-
793
- ```typescript
794
- import { init, SerpentStream, randomBytes } from 'leviathan-crypto';
795
-
796
- await init(['serpent', 'sha2']);
451
+ > CBC mode is unauthenticated. Use `Seal` with `SerpentCipher` for authenticated encryption.
797
452
 
798
- const key = randomBytes(32); // 32-byte key (HKDF handles expansion)
453
+ ### Example 4: Raw block operations (low-level)
799
454
 
800
- const stream = new SerpentStream();
801
-
802
- const plaintext = new Uint8Array(1024 * 1024); // 1 MB
803
- crypto.getRandomValues(plaintext);
804
-
805
- const ciphertext = stream.seal(key, plaintext); // default 64KB chunks
806
- const decrypted = stream.open(key, ciphertext);
807
-
808
- // decrypted is byte-identical to plaintext
809
-
810
- stream.dispose();
811
- ```
812
-
813
- ### Example 5: SerpentStreamPool (parallel chunked encryption)
814
-
815
- Use `SerpentStreamPool` for maximum throughput on multi-core hardware.
816
-
817
- ```typescript
818
- import { init, SerpentStreamPool, randomBytes } from 'leviathan-crypto';
819
-
820
- await init(['serpent', 'sha2']);
821
-
822
- const pool = await SerpentStreamPool.create({ workers: 4 });
823
-
824
- const key = randomBytes(32);
825
- const plaintext = new Uint8Array(10 * 1024 * 1024); // 10 MB
826
-
827
- const ciphertext = await pool.seal(key, plaintext);
828
- const decrypted = await pool.open(key, ciphertext);
829
-
830
- // decrypted is byte-identical to plaintext
831
-
832
- pool.dispose(); // terminates workers
833
- ```
834
-
835
- ### Example 6: SerpentStreamSealer / SerpentStreamOpener (incremental streaming)
836
-
837
- Use `SerpentStreamSealer` when data arrives in chunks and you cannot buffer the
838
- entire plaintext before encrypting — network streams, file processors, live feeds.
839
-
840
- ```typescript
841
- import { init, SerpentStreamSealer, SerpentStreamOpener, randomBytes } from 'leviathan-crypto';
842
-
843
- await init(['serpent', 'sha2']);
844
-
845
- const key = randomBytes(64); // 64-byte key
846
- const chunkSize = 65536; // 64 KB chunks
847
-
848
- // ── Seal side ────────────────────────────────────────────────────────────────
849
-
850
- const sealer = new SerpentStreamSealer(key, chunkSize);
851
- const header = sealer.header(); // transmit this to the opener first
852
-
853
- // seal() as data arrives — each chunk must be exactly chunkSize bytes
854
- const chunk0 = sealer.seal(plaintext0);
855
- const chunk1 = sealer.seal(plaintext1);
856
-
857
- // final() for the last chunk — may be shorter than chunkSize
858
- const lastChunk = sealer.final(lastPlaintext);
859
- // sealer is now dead — key material wiped
860
-
861
- // ── Open side ────────────────────────────────────────────────────────────────
862
-
863
- const opener = new SerpentStreamOpener(key, header);
864
-
865
- const pt0 = opener.open(chunk0);
866
- const pt1 = opener.open(chunk1);
867
- const ptN = opener.open(lastChunk); // opener detects isLast, wipes on return
868
- // opener is now dead
869
-
870
- // Truncation and reordering are detected — open() throws on auth failure
871
- ```
872
-
873
- To abort a stream mid-way (e.g. on connection drop):
874
-
875
- ```typescript
876
- sealer.dispose(); // wipes key material without producing a final chunk
877
- // opener will throw when it receives no more chunks
878
- ```
879
-
880
- ### Example 7: Raw block operations (low-level)
881
-
882
- Use the `Serpent` class for single 16-byte block operations. This is the lowest
883
- level API, most users should use `SerpentSeal` instead.
455
+ Use the `Serpent` class for single 16-byte block operations. This is the lowest-level
456
+ API; use `Seal` with `SerpentCipher` for most purposes.
884
457
 
885
458
  ```typescript
886
459
  import { init, Serpent } from 'leviathan-crypto';
460
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded';
887
461
 
888
- await init(['serpent']);
462
+ await init({ serpent: serpentWasm });
889
463
 
890
464
  const cipher = new Serpent();
891
465
 
@@ -915,13 +489,9 @@ cipher.dispose();
915
489
 
916
490
  | Condition | Error type | Message |
917
491
  |-----------|-----------|---------|
918
- | `SerpentSeal` constructed before `init(['serpent', 'sha2'])` | `Error` | `leviathan-crypto: call init(['serpent', 'sha2']) before using SerpentSeal` |
919
- | `SerpentSeal` key is not 64 bytes | `RangeError` | `SerpentSeal key must be 64 bytes (got N)` |
920
- | `SerpentSeal` data too short for decrypt | `RangeError` | `SerpentSeal ciphertext too short` |
921
- | `SerpentSeal` authentication failed | `Error` | `SerpentSeal: authentication failed` |
922
- | `init(['serpent'])` not called before constructing `Serpent` | `Error` | `leviathan-crypto: call init(['serpent']) before using this class` |
923
- | `SerpentCbc` constructed without `{ dangerUnauthenticated: true }` | `Error` | `leviathan-crypto: SerpentCbc is unauthenticated — use SerpentSeal instead. To use SerpentCbc directly, pass { dangerUnauthenticated: true }.` |
924
- | `SerpentCtr` constructed without `{ dangerUnauthenticated: true }` | `Error` | `leviathan-crypto: SerpentCtr is unauthenticated — use SerpentSeal instead. To use SerpentCtr directly, pass { dangerUnauthenticated: true }.` |
492
+ | `init({ serpent: ... })` not called before constructing `Serpent` | `Error` | `leviathan-crypto: call init({ serpent: ... }) before using this class` |
493
+ | `SerpentCbc` constructed without `{ dangerUnauthenticated: true }` | `Error` | `leviathan-crypto: SerpentCbc is unauthenticated use Seal with SerpentCipher instead. To use SerpentCbc directly, pass { dangerUnauthenticated: true }.` |
494
+ | `SerpentCtr` constructed without `{ dangerUnauthenticated: true }` | `Error` | `leviathan-crypto: SerpentCtr is unauthenticated — use Seal with SerpentCipher instead. To use SerpentCtr directly, pass { dangerUnauthenticated: true }.` |
925
495
  | Key is not 16, 24, or 32 bytes (`Serpent.loadKey`) | `RangeError` | `key must be 16, 24, or 32 bytes (got N)` |
926
496
  | Key is not 16, 24, or 32 bytes (`SerpentCbc`) | `RangeError` | `Serpent key must be 16, 24, or 32 bytes (got N)` |
927
497
  | Key is not 16, 24, or 32 bytes (`SerpentCtr`) | `RangeError` | `key must be 16, 24, or 32 bytes` |
@@ -931,38 +501,19 @@ cipher.dispose();
931
501
  | IV is not 16 bytes (`SerpentCbc`) | `RangeError` | `CBC IV must be 16 bytes (got N)` |
932
502
  | Ciphertext length is zero or not a multiple of 16 (`SerpentCbc.decrypt`) | `RangeError` | `ciphertext length must be a non-zero multiple of 16` |
933
503
  | Invalid PKCS7 padding on decrypt (`SerpentCbc.decrypt`) | `RangeError` | `invalid PKCS7 padding` |
934
- | `SerpentStream` constructed before `init(['serpent', 'sha2'])` | `Error` | `leviathan-crypto: call init(['serpent', 'sha2']) before using SerpentStream` |
935
- | `SerpentStream` key is not 32 bytes | `RangeError` | `SerpentStream key must be 32 bytes (got N)` |
936
- | `SerpentStream` chunkSize out of range | `RangeError` | `SerpentStream chunkSize must be 1024..65536 (got N)` |
937
- | `SerpentStream` ciphertext too short | `RangeError` | `SerpentStream: ciphertext too short` |
938
- | `SerpentStream` authentication failed | `Error` | `SerpentStream: authentication failed` |
939
- | `SerpentStreamPool.create()` before `init(['serpent', 'sha2'])` | `Error` | `leviathan-crypto: call init(['serpent', 'sha2']) before using SerpentStreamPool` |
940
- | `SerpentStreamPool` methods after `dispose()` | `Error` | `leviathan-crypto: pool is disposed` |
941
- | `SerpentStreamSealer` constructed before `init(['serpent', 'sha2'])` | `Error` | `leviathan-crypto: call init(['serpent']) before using SerpentStreamSealer` |
942
- | `SerpentStreamSealer` key is not 64 bytes | `RangeError` | `SerpentStreamSealer key must be 64 bytes (got N)` |
943
- | `SerpentStreamSealer` chunkSize out of range | `RangeError` | `SerpentStreamSealer chunkSize must be 1024..65536 (got N)` |
944
- | `SerpentStreamSealer.header()` called twice | `Error` | `SerpentStreamSealer: header() already called` |
945
- | `SerpentStreamSealer.seal()` before `header()` | `Error` | `SerpentStreamSealer: call header() first` |
946
- | `SerpentStreamSealer.seal()` or `final()` after `final()` or `dispose()` | `Error` | `SerpentStreamSealer: stream is closed` |
947
- | `SerpentStreamSealer.seal()` wrong plaintext size | `RangeError` | `SerpentStreamSealer: seal() requires exactly N bytes (got M)` |
948
- | `SerpentStreamSealer.final()` plaintext exceeds chunkSize | `RangeError` | `SerpentStreamSealer: final() plaintext exceeds chunkSize (got N)` |
949
- | `SerpentStreamOpener` constructed before `init(['serpent', 'sha2'])` | `Error` | `leviathan-crypto: call init(['serpent']) before using SerpentStreamOpener` |
950
- | `SerpentStreamOpener` key is not 64 bytes | `RangeError` | `SerpentStreamOpener key must be 64 bytes (got N)` |
951
- | `SerpentStreamOpener` header is not 20 bytes | `RangeError` | `SerpentStreamOpener header must be 20 bytes (got N)` |
952
- | `SerpentStreamOpener.open()` authentication failed | `Error` | `SerpentStreamOpener: authentication failed` |
953
- | `SerpentStreamOpener.open()` after stream closed | `Error` | `SerpentStreamOpener: stream is closed` |
954
504
 
955
505
  ---
956
506
 
957
507
  > ## Cross-References
958
508
  >
959
509
  > - [index](./README.md) — Project Documentation index
510
+ > - [lexicon](./lexicon.md) — Glossary of cryptographic terms
960
511
  > - [architecture](./architecture.md) — architecture overview, module relationships, buffer layouts, and build pipeline
961
512
  > - [asm_serpent](./asm_serpent.md) — WASM implementation details and buffer layout
962
513
  > - [serpent_reference](./serpent_reference.md) — algorithm specification, S-boxes, linear transform, and known attacks
963
514
  > - [serpent_audit](./serpent_audit.md) — security audit findings (correctness, side-channel analysis)
964
- > - [chacha20](./chacha20.md) — XChaCha20Poly1305 authenticated encryption (alternative AEAD)
965
- > - [sha2](./sha2.md) — HMAC-SHA256 and HKDF used internally by SerpentSeal and SerpentStream
515
+ > - [authenticated encryption](./aead.md) — `Seal`, `SealStream`, `OpenStream`: use `SerpentCipher` as the suite argument
516
+ > - [chacha20](./chacha20.md) — `XChaCha20Cipher`: alternative `CipherSuite` for `Seal` and streaming
517
+ > - [sha2](./sha2.md) — HMAC-SHA256 and HKDF used internally by `SerpentCipher`
966
518
  > - [types](./types.md) — `Blockcipher`, `Streamcipher`, and `AEAD` interfaces implemented by Serpent classes
967
519
  > - [utils](./utils.md) — `constantTimeEqual`, `wipe`, `randomBytes` used by Serpent wrappers
968
-