leviathan-crypto 1.4.0 → 2.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 (119) hide show
  1. package/CLAUDE.md +129 -94
  2. package/README.md +166 -223
  3. package/SECURITY.md +85 -45
  4. package/dist/chacha20/cipher-suite.d.ts +4 -0
  5. package/dist/chacha20/cipher-suite.js +78 -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 +320 -0
  17. package/dist/docs/architecture.md +419 -285
  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 +93 -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/errors.d.ts +10 -0
  32. package/dist/errors.js +38 -0
  33. package/dist/fortuna.d.ts +0 -6
  34. package/dist/fortuna.js +5 -5
  35. package/dist/index.d.ts +25 -9
  36. package/dist/index.js +36 -7
  37. package/dist/init.d.ts +3 -7
  38. package/dist/init.js +18 -35
  39. package/dist/keccak/embedded.d.ts +1 -0
  40. package/dist/keccak/embedded.js +27 -0
  41. package/dist/keccak/index.d.ts +4 -0
  42. package/dist/keccak/index.js +31 -0
  43. package/dist/kyber/embedded.d.ts +1 -0
  44. package/dist/kyber/embedded.js +27 -0
  45. package/dist/kyber/indcpa.d.ts +49 -0
  46. package/dist/kyber/indcpa.js +352 -0
  47. package/dist/kyber/index.d.ts +38 -0
  48. package/dist/kyber/index.js +150 -0
  49. package/dist/kyber/kem.d.ts +21 -0
  50. package/dist/kyber/kem.js +160 -0
  51. package/dist/kyber/params.d.ts +14 -0
  52. package/dist/kyber/params.js +37 -0
  53. package/dist/kyber/suite.d.ts +13 -0
  54. package/dist/kyber/suite.js +93 -0
  55. package/dist/kyber/types.d.ts +98 -0
  56. package/dist/kyber/types.js +25 -0
  57. package/dist/kyber/validate.d.ts +19 -0
  58. package/dist/kyber/validate.js +68 -0
  59. package/dist/kyber.wasm +0 -0
  60. package/dist/loader.d.ts +15 -6
  61. package/dist/loader.js +65 -21
  62. package/dist/serpent/cipher-suite.d.ts +4 -0
  63. package/dist/serpent/cipher-suite.js +121 -0
  64. package/dist/serpent/embedded.d.ts +1 -0
  65. package/dist/serpent/embedded.js +27 -0
  66. package/dist/serpent/index.d.ts +6 -37
  67. package/dist/serpent/index.js +9 -118
  68. package/dist/serpent/pool-worker.d.ts +1 -0
  69. package/dist/serpent/pool-worker.js +202 -0
  70. package/dist/serpent/serpent-cbc.d.ts +30 -0
  71. package/dist/serpent/serpent-cbc.js +136 -0
  72. package/dist/sha2/embedded.d.ts +1 -0
  73. package/dist/sha2/embedded.js +27 -0
  74. package/dist/sha2/hkdf.js +6 -2
  75. package/dist/sha2/index.d.ts +3 -2
  76. package/dist/sha2/index.js +3 -4
  77. package/dist/sha3/embedded.d.ts +1 -0
  78. package/dist/sha3/embedded.js +27 -0
  79. package/dist/sha3/index.d.ts +3 -2
  80. package/dist/sha3/index.js +3 -4
  81. package/dist/stream/constants.d.ts +6 -0
  82. package/dist/stream/constants.js +30 -0
  83. package/dist/stream/header.d.ts +9 -0
  84. package/dist/stream/header.js +77 -0
  85. package/dist/stream/index.d.ts +7 -0
  86. package/dist/stream/index.js +27 -0
  87. package/dist/stream/open-stream.d.ts +21 -0
  88. package/dist/stream/open-stream.js +146 -0
  89. package/dist/stream/seal-stream-pool.d.ts +38 -0
  90. package/dist/stream/seal-stream-pool.js +391 -0
  91. package/dist/stream/seal-stream.d.ts +20 -0
  92. package/dist/stream/seal-stream.js +142 -0
  93. package/dist/stream/seal.d.ts +9 -0
  94. package/dist/stream/seal.js +75 -0
  95. package/dist/stream/types.d.ts +24 -0
  96. package/dist/stream/types.js +26 -0
  97. package/dist/utils.d.ts +7 -2
  98. package/dist/utils.js +49 -3
  99. package/dist/wasm-source.d.ts +12 -0
  100. package/dist/wasm-source.js +26 -0
  101. package/package.json +13 -5
  102. package/dist/chacha20/pool.d.ts +0 -52
  103. package/dist/chacha20/pool.js +0 -178
  104. package/dist/chacha20/pool.worker.js +0 -37
  105. package/dist/chacha20/stream-sealer.d.ts +0 -49
  106. package/dist/chacha20/stream-sealer.js +0 -327
  107. package/dist/docs/chacha20_pool.md +0 -309
  108. package/dist/docs/wasm.md +0 -194
  109. package/dist/serpent/seal.d.ts +0 -8
  110. package/dist/serpent/seal.js +0 -72
  111. package/dist/serpent/stream-pool.d.ts +0 -48
  112. package/dist/serpent/stream-pool.js +0 -275
  113. package/dist/serpent/stream-sealer.d.ts +0 -55
  114. package/dist/serpent/stream-sealer.js +0 -342
  115. package/dist/serpent/stream.d.ts +0 -28
  116. package/dist/serpent/stream.js +0 -205
  117. package/dist/serpent/stream.worker.d.ts +0 -32
  118. package/dist/serpent/stream.worker.js +0 -117
  119. /package/dist/chacha20/{pool.worker.d.ts → pool-worker.d.ts} +0 -0
package/dist/docs/sha3.md CHANGED
@@ -1,27 +1,36 @@
1
1
  # SHA3 TypeScript API Reference
2
2
 
3
3
  > [!NOTE]
4
- > SHA-3 hash functions and SHAKE XOFs (TypeScript API)
5
- >
6
- > See [SHA-3 implementation audit](./sha3_audit.md) for algorithm correctness verifications.
4
+ > Covers the SHA-3 hash functions (SHA3-224 through SHA3-512) and the SHAKE extendable-output functions (SHAKE128, SHAKE256). See [SHA-3 implementation audit](./sha3_audit.md) for algorithm correctness verifications.
5
+
6
+ > ### Table of Contents
7
+ > - [Overview](#overview)
8
+ > - [Security Notes](#security-notes)
9
+ > - [Module Init](#module-init)
10
+ > - [API Reference](#api-reference)
11
+ > - [Incremental XOF API](#incremental-xof-api-absorb--squeeze--reset)
12
+ > - [Usage Examples](#usage-examples)
13
+ > - [Error Conditions](#error-conditions)
14
+
15
+ ---
7
16
 
8
17
  ## Overview
9
18
 
10
19
  The SHA-3 family provides six hash functions standardized in **FIPS 202**: four
11
20
  fixed-output hash functions (SHA3-224, SHA3-256, SHA3-384, SHA3-512) and two
12
21
  extendable-output functions, or XOFs (SHAKE128, SHAKE256). All six are built on
13
- the **Keccak sponge construction** -- a fundamentally different design from the
22
+ the **Keccak sponge construction**, a fundamentally different design from the
14
23
  Merkle-Damgard structure used by SHA-2.
15
24
 
16
25
  SHA-3 is **not** a replacement for SHA-2. Both are considered secure, and both are
17
26
  standardized by NIST. SHA-3 exists to provide **defense-in-depth**: if a flaw is
18
27
  ever discovered in SHA-2, SHA-3 is completely unaffected because it uses a different
19
- mathematical foundation. Think of it as insurance -- you may never need it, but if
20
- you do, you will be very glad it is there.
28
+ mathematical foundation. You may never need that insurance, but if you do, you will be
29
+ very glad it is there.
21
30
 
22
31
  The SHAKE XOFs are particularly flexible. Unlike SHA3-256, which always produces
23
- exactly 32 bytes, SHAKE128 and SHAKE256 can produce variable-length output -- you
24
- tell them how many bytes you want. This is useful for key derivation, generating
32
+ exactly 32 bytes, SHAKE128 and SHAKE256 can produce variable-length output. You
33
+ tell them how many bytes you want, making them useful for key derivation, generating
25
34
  nonces, or any situation where you need more (or fewer) bytes than a standard hash
26
35
  provides.
27
36
 
@@ -41,7 +50,7 @@ secret. SHA-3's sponge construction makes this impossible.
41
50
  - **Length extension immunity.** Unlike SHA-2, the SHA-3 sponge construction does
42
51
  not leak enough internal state for length extension attacks. Computing
43
52
  `SHA3(secret + message)` does not let an attacker forge `SHA3(secret + message + extra)`.
44
- That said, **HMAC is still the correct way to build a MAC** -- do not use raw
53
+ That said, **HMAC is still the correct way to build a MAC** do not use raw
45
54
  `SHA3(key + message)` as a MAC construction, even though it is not vulnerable to
46
55
  length extension. HMAC provides a formally proven security reduction.
47
56
 
@@ -50,7 +59,7 @@ secret. SHA-3's sponge construction makes this impossible.
50
59
  sponge directly with `absorb()` / `squeeze()`. The only constraint is
51
60
  `outputLength >= 1`.
52
61
 
53
- - **Not for password hashing.** SHA-3 is a fast hash that is the opposite of
62
+ - **Not for password hashing.** SHA-3 is a fast hash, which is the opposite of
54
63
  what you want for password storage. Passwords must be hashed with a slow,
55
64
  memory-hardened algorithm like **Argon2id**. See [argon2id.md](./argon2id.md) for
56
65
  usage patterns including passphrase-based encryption with leviathan primitives.
@@ -68,27 +77,45 @@ secret. SHA-3's sponge construction makes this impossible.
68
77
  Each module subpath exports its own init function for consumers who want
69
78
  tree-shakeable imports.
70
79
 
71
- ### `sha3Init(mode?, opts?)`
80
+ ### `sha3Init(source)`
72
81
 
73
82
  Initializes only the sha3 WASM binary. Equivalent to calling the
74
- root `init(['sha3'], mode, opts)` but without pulling the other three
83
+ root `init({ sha3: source })` but without pulling the other three
75
84
  modules into the bundle.
76
85
 
77
86
  **Signature:**
78
87
 
79
88
  ```typescript
80
- async function sha3Init(mode?: Mode, opts?: InitOpts): Promise<void>
89
+ async function sha3Init(source: WasmSource): Promise<void>
81
90
  ```
82
91
 
83
92
  **Usage:**
84
93
 
85
94
  ```typescript
86
95
  import { sha3Init, SHA3_256 } from 'leviathan-crypto/sha3'
96
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
87
97
 
88
- await sha3Init()
98
+ await sha3Init(sha3Wasm)
89
99
  const sha3 = new SHA3_256()
90
100
  ```
91
101
 
102
+ ### keccakInit() Alias
103
+
104
+ `'keccak'` is an alias for `'sha3'`. Same WASM binary, same instance slot.
105
+ `keccakInit()` and `sha3Init()` are interchangeable.
106
+
107
+ ```typescript
108
+ import { keccakInit, SHAKE256, SHA3_256 } from 'leviathan-crypto/keccak'
109
+ import { keccakWasm } from 'leviathan-crypto/keccak/embedded'
110
+
111
+ await keccakInit(keccakWasm)
112
+ // isInitialized('sha3') === true — same slot
113
+ ```
114
+
115
+ Use the `keccak` subpath when the consuming context (such as ML-KEM) makes the
116
+ Keccak primitive name semantically clearer. See [init.md](./init.md#keccak-alias-for-ml-kem)
117
+ for full details.
118
+
92
119
  ---
93
120
 
94
121
  ## API Reference
@@ -97,19 +124,18 @@ All SHA-3 classes require initialization before use. Either the root `init()`:
97
124
 
98
125
  ```typescript
99
126
  import { init } from 'leviathan-crypto'
127
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
100
128
 
101
- await init('sha3')
129
+ await init({ sha3: sha3Wasm })
102
130
  ```
103
131
 
104
- Both `init('sha3')` and `init(['sha3'])` are valid — the root `init()` accepts
105
- a single `Module` string or an array.
106
-
107
132
  Or the subpath `sha3Init()`:
108
133
 
109
134
  ```typescript
110
135
  import { sha3Init } from 'leviathan-crypto/sha3'
136
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
111
137
 
112
- await sha3Init()
138
+ await sha3Init(sha3Wasm)
113
139
  ```
114
140
 
115
141
  If you use SHA-3 classes without calling `init()` first, the constructor
@@ -134,7 +160,7 @@ class SHA3_224 {
134
160
  ### SHA3_256
135
161
 
136
162
  Fixed-output hash function. Produces a **32-byte** (256-bit) digest. This is the
137
- most commonly used SHA-3 variant -- 256-bit security is suitable for most
163
+ most commonly used SHA-3 variant; 256-bit security is suitable for most
138
164
  applications.
139
165
 
140
166
  ```typescript
@@ -255,8 +281,9 @@ Calling `absorb()` while squeezing throws:
255
281
 
256
282
  ```typescript
257
283
  import { init, SHAKE256 } from 'leviathan-crypto'
284
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
258
285
 
259
- await init('sha3')
286
+ await init({ sha3: sha3Wasm })
260
287
 
261
288
  const xof = new SHAKE256()
262
289
  xof.absorb(ikm) // input key material
@@ -275,13 +302,14 @@ xof.dispose()
275
302
 
276
303
  ### Example 1: Hash a string with SHA3-256
277
304
 
278
- The most common use case -- hash some data and get a hex digest.
305
+ The most common use case: hash some data and get a hex digest.
279
306
 
280
307
  ```typescript
281
308
  import { init, SHA3_256, bytesToHex, utf8ToBytes } from 'leviathan-crypto'
309
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
282
310
 
283
311
  // Initialize the SHA-3 WASM module (once, at startup)
284
- await init('sha3')
312
+ await init({ sha3: sha3Wasm })
285
313
 
286
314
  // Create a hasher
287
315
  const sha3 = new SHA3_256()
@@ -303,8 +331,9 @@ sha3.dispose()
303
331
 
304
332
  ```typescript
305
333
  import { init, SHA3_512, bytesToHex } from 'leviathan-crypto'
334
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
306
335
 
307
- await init('sha3')
336
+ await init({ sha3: sha3Wasm })
308
337
 
309
338
  const sha3 = new SHA3_512()
310
339
 
@@ -322,13 +351,14 @@ sha3.dispose()
322
351
 
323
352
  ### Example 3: Hash multiple messages
324
353
 
325
- Each call to `hash()` is independent -- the internal state is reset automatically.
354
+ Each call to `hash()` is independent; the internal state resets automatically.
326
355
  You can reuse the same class instance for multiple hashes.
327
356
 
328
357
  ```typescript
329
358
  import { init, SHA3_256, bytesToHex, utf8ToBytes } from 'leviathan-crypto'
359
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
330
360
 
331
- await init('sha3')
361
+ await init({ sha3: sha3Wasm })
332
362
 
333
363
  const sha3 = new SHA3_256()
334
364
 
@@ -354,8 +384,9 @@ for key derivation or generating fixed-size tokens.
354
384
 
355
385
  ```typescript
356
386
  import { init, SHAKE128, bytesToHex, utf8ToBytes } from 'leviathan-crypto'
387
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
357
388
 
358
- await init('sha3')
389
+ await init({ sha3: sha3Wasm })
359
390
 
360
391
  const shake = new SHAKE128()
361
392
 
@@ -383,8 +414,9 @@ shake.dispose()
383
414
 
384
415
  ```typescript
385
416
  import { init, SHAKE256, bytesToHex } from 'leviathan-crypto'
417
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
386
418
 
387
- await init('sha3')
419
+ await init({ sha3: sha3Wasm })
388
420
 
389
421
  const shake = new SHAKE256()
390
422
 
@@ -400,17 +432,18 @@ shake.dispose()
400
432
 
401
433
  ---
402
434
 
403
- ### Example 6: SHA-256 vs SHA3-256 -- different algorithms, different output
435
+ ### Example 6: SHA-256 vs SHA3-256
404
436
 
405
437
  SHA-256 (from the SHA-2 family) and SHA3-256 are completely different algorithms.
406
- They produce different output for the same input. Neither is "better" -- both
407
- are secure. SHA3-256 adds defense-in-depth.
438
+ They produce different output for the same input. Both are secure; SHA3-256 adds defense-in-depth.
408
439
 
409
440
  ```typescript
410
441
  import { init, SHA256, SHA3_256, bytesToHex, utf8ToBytes } from 'leviathan-crypto'
442
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
443
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
411
444
 
412
445
  // Initialize both modules
413
- await init(['sha2', 'sha3'])
446
+ await init({ sha2: sha2Wasm, sha3: sha3Wasm })
414
447
 
415
448
  const sha2 = new SHA256()
416
449
  const sha3 = new SHA3_256()
@@ -437,8 +470,9 @@ deterministic output.
437
470
 
438
471
  ```typescript
439
472
  import { init, SHA3_256, bytesToHex } from 'leviathan-crypto'
473
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
440
474
 
441
- await init('sha3')
475
+ await init({ sha3: sha3Wasm })
442
476
 
443
477
  const sha3 = new SHA3_256()
444
478
  const digest = sha3.hash(new Uint8Array(0))
@@ -453,16 +487,16 @@ sha3.dispose()
453
487
 
454
488
  ## Error Conditions
455
489
 
456
- ### `init('sha3')` not called
490
+ ### SHA-3 module not initialized
457
491
 
458
492
  If you construct a SHA-3 class before initializing the module, the constructor
459
493
  throws immediately:
460
494
 
461
495
  ```
462
- Error: leviathan-crypto: call init(['sha3']) before using this class
496
+ Error: leviathan-crypto: call init({ sha3: ... }) before using this class
463
497
  ```
464
498
 
465
- **Fix:** Call `await init('sha3')` once at application startup, before creating
499
+ **Fix:** Call `await init({ sha3: sha3Wasm })` once at application startup, before creating
466
500
  any SHA-3 class instances.
467
501
 
468
502
  ---
@@ -1,21 +1,23 @@
1
- # Public TypeScript interfaces for cryptographic primitives
1
+ # TypeScript Interfaces
2
2
 
3
- ## Overview
3
+ > [!NOTE]
4
+ > Defines the abstract interfaces all leviathan-crypto cryptographic classes implement. These are type-only exports; they contain no runtime code and generate no JavaScript output.
4
5
 
5
- This module defines the abstract interfaces that all leviathan-crypto cryptographic classes implement. These are **type-only exports** -- they contain no runtime code and generate no JavaScript output.
6
-
7
- Use these interfaces when you need to write generic code that works with any hash function, any cipher, or any AEAD scheme without depending on a specific implementation. They are available immediately on import with no `init()` call required.
8
-
9
- ---
10
-
11
- ## Security Notes
12
-
13
- This module contains type definitions only. There are no security-sensitive operations.
6
+ > ### Table of Contents
7
+ > - [API Reference](#api-reference)
8
+ > - [Usage Examples](#usage-examples)
9
+ > - [WasmSource](#wasmsource)
10
+ > - [CipherSuite](#ciphersuite)
11
+ > - [DerivedKeys](#derivedkeys)
12
+ > - [SealStreamOpts](#sealstreamopts)
13
+ > - [PoolOpts](#poolopts)
14
14
 
15
15
  ---
16
16
 
17
17
  ## API Reference
18
18
 
19
+ Use these interfaces when you need generic code that works with any hash function, any cipher, or any AEAD scheme without depending on a specific implementation. They are available immediately on import with no `init()` call required.
20
+
19
21
  ### Hash
20
22
 
21
23
  ```typescript
@@ -45,7 +47,7 @@ interface KeyedHash {
45
47
 
46
48
  Interface for keyed hash functions / MACs (e.g., HMAC-SHA256, HMAC-SHA512).
47
49
 
48
- Note: `KeyedHash` does **not** extend `Hash`. Its `hash` method takes a `key` parameter in addition to the message.
50
+ `KeyedHash` does **not** extend `Hash`. Its `hash` method takes a `key` parameter in addition to the message.
49
51
 
50
52
  | Method | Description |
51
53
  |---|---|
@@ -109,7 +111,7 @@ Interface for authenticated encryption with associated data (e.g., XChaCha20-Pol
109
111
  | Method | Description |
110
112
  |---|---|
111
113
  | `encrypt(msg, aad?)` | Encrypts `msg` and authenticates both `msg` and optional `aad`. Returns ciphertext with appended authentication tag. |
112
- | `decrypt(ciphertext, aad?)` | Decrypts and verifies the authentication tag. Returns plaintext on success. Throws `Error` on authentication failure never returns null. |
114
+ | `decrypt(ciphertext, aad?)` | Decrypts and verifies the authentication tag. Returns plaintext on success. Throws `Error` on authentication failure. Never returns null. |
113
115
  | `dispose()` | Releases WASM resources and wipes internal buffers. |
114
116
 
115
117
  ---
@@ -128,7 +130,7 @@ function digestAndLog(hasher: Hash, data: Uint8Array): Uint8Array {
128
130
  }
129
131
  ```
130
132
 
131
- This function accepts any `Hash` implementation -- `SHA256`, `SHA512`, `SHA3_256`, etc. -- without importing any of them directly.
133
+ This function accepts any `Hash` implementation (`SHA256`, `SHA512`, `SHA3_256`, etc.) without importing any of them directly.
132
134
 
133
135
  ---
134
136
 
@@ -142,7 +144,7 @@ function sealMessage(aead: AEAD, plaintext: Uint8Array, metadata: Uint8Array): U
142
144
  }
143
145
 
144
146
  function openMessage(aead: AEAD, ciphertext: Uint8Array, metadata: Uint8Array): Uint8Array {
145
- // decrypt() throws on auth failure no null check needed
147
+ // decrypt() throws on auth failure, no null check needed
146
148
  return aead.decrypt(ciphertext, metadata)
147
149
  }
148
150
  ```
@@ -186,13 +188,88 @@ function cleanup(ctx: EncryptionContext): void {
186
188
 
187
189
  ---
188
190
 
191
+ ## WasmSource
192
+
193
+ Union type for WASM module sources. Accepted by `init()`, `serpentInit()`, etc.
194
+
195
+ `string | URL | ArrayBuffer | Uint8Array | WebAssembly.Module | Response | Promise<Response>`
196
+
197
+ ---
198
+
199
+ ## CipherSuite
200
+
201
+ Cipher-specific logic injected into `SealStream` and `OpenStream`.
202
+
203
+ | Field | Type | Description |
204
+ |-------|------|-------------|
205
+ | `formatEnum` | `number` | Wire format ID encoded in header byte 0 bits 0-5 (max 0x3f): bits 0-3 = cipher nibble (0x1=xchacha20, 0x2=serpent), bits 4-5 = KEM selector (0x00=none, 0x10=ML-KEM-512, 0x20=ML-KEM-768, 0x30=ML-KEM-1024), bit 6 reserved |
206
+ | `formatName` | `string` | Human-readable label, e.g. `'xchacha20'`, `'serpent'`, `'mlkem768+xchacha20'` |
207
+ | `hkdfInfo` | `string` | HKDF info string for key derivation |
208
+ | `keySize` | `number` | Seal/encrypt key size in bytes (encapsulation key bytes for KEM suites) |
209
+ | `decKeySize` | `number \| undefined` | Open/decrypt key size in bytes (decapsulation key bytes for KEM suites). Absent → same as `keySize` (symmetric case) |
210
+ | `kemCtSize` | `number` | KEM ciphertext byte length appended to the header in the preamble. `0` for symmetric suites |
211
+ | `tagSize` | `number` | Authentication tag size in bytes |
212
+ | `padded` | `boolean` | Whether ciphertext includes padding (PKCS7 for CBC) |
213
+ | `wasmModules` | `readonly string[]` | Cipher-specific WASM modules used by pool workers and per-chunk operations (not transitive dependencies such as HKDF-SHA-256 used by `deriveKeys()`) |
214
+
215
+ | Method | Signature | Description |
216
+ |--------|-----------|-------------|
217
+ | `deriveKeys` | `(masterKey, nonce) → DerivedKeys` | HKDF key derivation |
218
+ | `sealChunk` | `(keys, counterNonce, chunk, aad?) → Uint8Array` | Encrypt one chunk |
219
+ | `openChunk` | `(keys, counterNonce, chunk, aad?) → Uint8Array` | Decrypt one chunk |
220
+ | `wipeKeys` | `(keys) → void` | Zero derived key material |
221
+ | `createPoolWorker` | `() → Worker` | Create a Web Worker for pool use |
222
+
223
+ Implementations: `XChaCha20Cipher`, `SerpentCipher` (plain `const` objects, not classes), and `KyberSuite` (factory function returning a `CipherSuite`). See [ciphersuite.md](./ciphersuite.md).
224
+
225
+ > [!IMPORTANT]
226
+ > All CipherSuite implementations use HKDF-SHA-256 in `deriveKeys()`. The stream layer requires
227
+ > `sha2` to be initialized regardless of which cipher is selected.
228
+
229
+ ---
230
+
231
+ ## DerivedKeys
232
+
233
+ Opaque key material returned by `CipherSuite.deriveKeys()`.
234
+
235
+ | Field | Type | Description |
236
+ |-------|------|-------------|
237
+ | `bytes` | `readonly Uint8Array` | Raw derived key bytes (opaque to the stream layer) |
238
+
239
+ ---
240
+
241
+ ## SealStreamOpts
242
+
243
+ Options for `SealStream` constructor.
244
+
245
+ | Field | Type | Default | Description |
246
+ |-------|------|---------|-------------|
247
+ | `chunkSize` | `number` | `65536` | Chunk size in bytes. Range: [1024, 16777215]. |
248
+ | `framed` | `boolean` | `false` | Enable u32be length-prefixed framing. |
249
+
250
+ ---
251
+
252
+ ## PoolOpts
253
+
254
+ Options for `SealStreamPool.create()`.
255
+
256
+ | Field | Type | Default | Description |
257
+ |-------|------|---------|-------------|
258
+ | `wasm` | `WasmSource \| Record<string, WasmSource>` | | WASM module source(s). Single source for single-module ciphers, Record for multi-module. |
259
+ | `workers` | `number` | `navigator.hardwareConcurrency ?? 4` | Number of Web Workers. |
260
+ | `chunkSize` | `number` | `65536` | Chunk size in bytes. |
261
+ | `framed` | `boolean` | `false` | Enable framed mode. |
262
+ | `jobTimeout` | `number` | `30000` | Per-job timeout in milliseconds. |
263
+
264
+ ---
265
+
189
266
  > ## Cross-References
190
267
  >
191
268
  > - [index](./README.md) — Project Documentation index
192
269
  > - [architecture](./architecture.md) — architecture overview, module relationships, buffer layouts, and build pipeline
193
270
  > - [utils](./utils.md) — encoding utilities and `constantTimeEqual` for verifying MACs from `KeyedHash`
194
271
  > - [serpent](./serpent.md) — Serpent classes implement `Blockcipher`, `Streamcipher`, and `AEAD`
195
- > - [chacha20](./chacha20.md) — `XChaCha20Seal` and `XChaCha20StreamSealer`/`Opener` implement `AEAD`; `ChaCha20`/`ChaCha20Poly1305`/`XChaCha20Poly1305` are stateless primitives
272
+ > - [chacha20](./chacha20.md) — `XChaCha20Cipher` is a `CipherSuite` for `SealStream`/`OpenStream`/`Seal`; `Seal` provides one-shot AEAD over any `CipherSuite`; `ChaCha20`/`ChaCha20Poly1305`/`XChaCha20Poly1305` are stateless primitives
196
273
  > - [sha2](./sha2.md) — SHA-2 classes implement `Hash`; HMAC classes implement `KeyedHash`
197
274
  > - [sha3](./sha3.md) — SHA-3 classes implement `Hash`; SHAKE classes extend with XOF API
198
275
  > - [test-suite](./test-suite.md) — test suite structure and vector corpus
@@ -1,23 +1,23 @@
1
- # Encoding utilities, comparison functions, and random byte generation
1
+ # Utilities
2
2
 
3
- ## Overview
3
+ > [!NOTE]
4
+ > Pure TypeScript utilities that ship alongside the WASM-backed primitives. No `init()` call required; all functions work immediately on import.
4
5
 
5
- Pure TypeScript utilities that ship alongside the WASM-backed primitives. These functions have **no `init()` dependency** -- they work immediately on import, without loading any WASM module.
6
-
7
- The module covers four areas:
8
-
9
- - **Encoding** -- hex, UTF-8, and base64 conversions between strings and `Uint8Array`
10
- - **Security** -- constant-time comparison and secure memory wiping
11
- - **Byte manipulation** -- XOR and concatenation of byte arrays
12
- - **Random** -- cryptographically secure random byte generation
6
+ > ### Table of Contents
7
+ > - [Security Notes](#security-notes)
8
+ > - [API Reference](#api-reference)
9
+ > - [Usage Examples](#usage-examples)
10
+ > - [Error Conditions](#error-conditions)
13
11
 
14
12
  ---
15
13
 
14
+ The module covers encoding (hex, UTF-8, and base64 conversions between strings and `Uint8Array`), security (constant-time comparison and secure memory wiping), byte manipulation (XOR and concatenation), and random byte generation.
15
+
16
16
  ## Security Notes
17
17
 
18
- **`constantTimeEqual`** uses an XOR-accumulate pattern with no early return on byte mismatch. Use this function whenever you compare MACs, hashes, authentication tags, or any secret-derived values. **Never** use `===`, `Buffer.equals`, or manual loop-with-break for security comparisons -- those leak timing information that can be exploited to recover secrets.
18
+ **`constantTimeEqual`** uses a WASM SIMD module when available to remove the JS JIT compiler from the timing picture, falling back to an XOR-accumulate loop on older runtimes. Use this function whenever you compare MACs, hashes, authentication tags, or any secret-derived values. Never use `===`, `Buffer.equals`, or a manual loop-with-break for security comparisons. Those leak timing information that attackers can exploit to recover secrets. Inputs are limited to [`CT_MAX_BYTES`](#ct_max_bytes) (32768 bytes) per side.
19
19
 
20
- The length check in `constantTimeEqual` is _not_ constant-time, because array length is non-secret in all standard protocols. If your use case treats length as secret, you must pad to equal length before comparing.
20
+ The length check in `constantTimeEqual` is not constant-time, because array length is non-secret in all standard protocols. If your use case treats length as secret, pad to equal length before comparing.
21
21
 
22
22
  **`wipe`** zeroes a typed array in-place. Call it on keys, plaintext buffers, and any other sensitive data as soon as you are done with them. JavaScript's garbage collector does not guarantee timely or complete erasure of memory.
23
23
 
@@ -75,7 +75,7 @@ Decodes UTF-8 bytes to a JavaScript string using the platform `TextDecoder`.
75
75
  base64ToBytes(b64: string): Uint8Array | undefined
76
76
  ```
77
77
 
78
- Decodes a base64 or base64url string to a `Uint8Array`. Handles padded, unpadded, and legacy `%3d` padding. Unpadded base64url input is accepted (RFC 4648 §5). Returns `undefined` if the input is not valid base64 (e.g., illegal characters or `rem=1` length).
78
+ Decodes a base64 or base64url string to a `Uint8Array`. Handles padded, unpadded, and legacy `%3d` padding. Unpadded base64url input is accepted (RFC 4648 §5). Returns `undefined` if the input is not valid base64 (illegal characters or `rem=1` length).
79
79
 
80
80
  ---
81
81
 
@@ -85,7 +85,7 @@ Decodes a base64 or base64url string to a `Uint8Array`. Handles padded, unpadded
85
85
  bytesToBase64(bytes: Uint8Array, url?: boolean): string
86
86
  ```
87
87
 
88
- Encodes a `Uint8Array` to a base64 string. Pass `url = true` for base64url (RFC 4648 §5 uses `-` and `_` instead of `+` and `/`, no padding characters). Defaults to standard base64.
88
+ Encodes a `Uint8Array` to a base64 string. Pass `url = true` for base64url (RFC 4648 §5), which uses `-` and `_` instead of `+` and `/` with no padding characters. Defaults to standard base64.
89
89
 
90
90
  ---
91
91
 
@@ -95,7 +95,40 @@ Encodes a `Uint8Array` to a base64 string. Pass `url = true` for base64url (RFC
95
95
  constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean
96
96
  ```
97
97
 
98
- Returns `true` if `a` and `b` contain identical bytes. Uses XOR-accumulate with no early return on mismatch. Returns `false` immediately if the arrays differ in length (length is non-secret).
98
+ Returns `true` if `a` and `b` contain identical bytes. Returns `false` immediately if the arrays differ in length (length is non-secret in all standard protocols).
99
+
100
+ When WebAssembly SIMD is available the comparison runs inside a WASM module, removing the JS JIT compiler from the timing picture. Speculative optimisation and branch prediction inside the engine cannot short-circuit the loop. On runtimes without SIMD support the function falls back to an XOR-accumulate loop in JavaScript, which is best-effort but not a hardware-level guarantee. The overall posture is best-available constant-time, not a cryptographic proof of timing safety.
101
+
102
+ Maximum input size is [`CT_MAX_BYTES`](#ct_max_bytes) (32768 bytes) per side. Throws `RangeError` if either array exceeds this limit.
103
+
104
+ Use this function when working with lower-level unauthenticated primitives or building custom authenticated protocols on top of the hashing and KDF APIs. Three common cases:
105
+
106
+ **Encrypt-then-MAC with `SerpentCbc` or `SerpentCtr`.** If you use the `dangerUnauthenticated` primitive directly and compute your own HMAC-SHA256 tag, compare that tag with `constantTimeEqual`. See the [example below](#encrypt-then-mac-with-serpentcbc).
107
+
108
+ **Argon2id key verification.** When re-deriving an Argon2id hash to verify a passphrase, the final comparison must be constant-time. See [argon2id.md](./argon2id.md#password-hashing-and-verification) for the full example.
109
+
110
+ **Custom HMAC protocols.** Any protocol where you derive a MAC with `HMAC_SHA256` or `HMAC_SHA512` and compare it against a received value. See [examples.md](./examples.md#hmac-sha256-message-authentication) for a complete example.
111
+
112
+ ---
113
+
114
+ ### CT_MAX_BYTES
115
+
116
+ ```typescript
117
+ const CT_MAX_BYTES: 32768
118
+ ```
119
+
120
+ Maximum input size accepted by [`constantTimeEqual`](#constanttimeequal) per side, in bytes. Reflects the physical layout of the WASM comparison module: one 64 KiB page of linear memory split equally between the two input buffers (32 KiB each).
121
+
122
+ In practice the largest comparison performed anywhere in this library is a 32-byte HMAC-SHA-256 tag. This limit only matters for custom protocols that compare unusually large values. Use this constant to guard your own inputs rather than hardcoding the magic number:
123
+
124
+ ```typescript
125
+ import { constantTimeEqual, CT_MAX_BYTES } from 'leviathan-crypto'
126
+
127
+ if (a.length > CT_MAX_BYTES || b.length > CT_MAX_BYTES) {
128
+ throw new RangeError(`comparison input exceeds CT_MAX_BYTES (${CT_MAX_BYTES})`)
129
+ }
130
+ const match = constantTimeEqual(a, b)
131
+ ```
99
132
 
100
133
  ---
101
134
 
@@ -115,7 +148,7 @@ Zeroes a typed array in-place by calling `fill(0)`. Use this to clear keys, plai
115
148
  xor(a: Uint8Array, b: Uint8Array): Uint8Array
116
149
  ```
117
150
 
118
- Returns a new `Uint8Array` where each byte is `a[i] ^ b[i]`. Both arrays must have the same length; throws `RangeError` if they differ.
151
+ Returns a new `Uint8Array` where each byte is `a[i] ^ b[i]`. Both arrays must have the same length. Throws `RangeError` if they differ.
119
152
 
120
153
  ---
121
154
 
@@ -125,7 +158,7 @@ Returns a new `Uint8Array` where each byte is `a[i] ^ b[i]`. Both arrays must ha
125
158
  concat(...arrays: Uint8Array[]): Uint8Array
126
159
  ```
127
160
 
128
- Concatenate one or more `Uint8Array`s into a new array.
161
+ Concatenates one or more `Uint8Array`s into a new array.
129
162
 
130
163
  ---
131
164
 
@@ -149,10 +182,10 @@ Returns `true` if the current runtime supports WebAssembly SIMD (the `v128`
149
182
  type and associated operations). The result is computed once on first call by
150
183
  validating a minimal v128 WASM module, then cached for subsequent calls.
151
184
 
152
- This function is called internally by `SerpentCtr.encryptChunk`,
153
- `SerpentCbc.decrypt`, and `ChaCha20.encryptChunk` to select the fast SIMD path
154
- at runtime. It is exported for informational purposes you do not need to call
155
- it yourself. SIMD dispatch is fully automatic.
185
+ `SerpentCtr.encryptChunk`, `SerpentCbc.decrypt`, and `ChaCha20.encryptChunk`
186
+ call this internally to select the fast SIMD path at runtime. It is exported
187
+ for informational purposes. You do not need to call it yourself. SIMD dispatch
188
+ is fully automatic.
156
189
 
157
190
  Supported in all modern browsers and Node.js 16+. Returns `false` in older
158
191
  environments, which fall back silently to the scalar path.
@@ -201,21 +234,62 @@ if (decoded) console.log(bytesToUtf8(decoded)) // "leviathan-crypto"
201
234
 
202
235
  ---
203
236
 
204
- ### Secure MAC comparison
237
+ ### Encrypt-then-MAC with SerpentCbc
238
+
239
+ If you use `SerpentCbc` or `SerpentCtr` directly with `{ dangerUnauthenticated: true }`, you are responsible for authentication. The correct pattern is Encrypt-then-MAC: encrypt first, then compute HMAC-SHA256 over the ciphertext, and use `constantTimeEqual` to verify on decrypt.
205
240
 
206
241
  ```typescript
207
- import { constantTimeEqual } from 'leviathan-crypto'
242
+ import {
243
+ init, SerpentCbc, HMAC_SHA256,
244
+ constantTimeEqual, randomBytes, wipe, concat,
245
+ } from 'leviathan-crypto'
246
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
247
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
248
+
249
+ await init({ serpent: serpentWasm, sha2: sha2Wasm })
250
+
251
+ const encKey = randomBytes(32)
252
+ const macKey = randomBytes(32)
253
+ const iv = randomBytes(16)
208
254
 
209
- // After computing a MAC over received data, compare it to the expected tag.
210
- // NEVER use === or .every() for this -- timing leaks enable forgery attacks.
211
- const computedMac: Uint8Array = hmac.hash(key, message)
212
- const receivedMac: Uint8Array = getTagFromNetwork()
255
+ // ── Encrypt ──────────────────────────────────────────────────────────────────
213
256
 
214
- if (!constantTimeEqual(computedMac, receivedMac)) {
215
- throw new Error('Authentication failed: MAC mismatch')
257
+ const cbc = new SerpentCbc({ dangerUnauthenticated: true })
258
+ const ct = cbc.encrypt(encKey, iv, plaintext)
259
+ cbc.dispose()
260
+
261
+ // MAC covers iv || ct so the IV is authenticated too
262
+ const hmac = new HMAC_SHA256()
263
+ const tag = hmac.hash(macKey, concat(iv, ct))
264
+ hmac.dispose()
265
+
266
+ const envelope = concat(iv, ct, tag) // store or transmit this
267
+
268
+ // ── Decrypt ──────────────────────────────────────────────────────────────────
269
+
270
+ const receivedIv = envelope.subarray(0, 16)
271
+ const receivedCt = envelope.subarray(16, envelope.length - 32)
272
+ const receivedTag = envelope.subarray(envelope.length - 32)
273
+
274
+ const hmac2 = new HMAC_SHA256()
275
+ const expectedTag = hmac2.hash(macKey, concat(receivedIv, receivedCt))
276
+ hmac2.dispose()
277
+
278
+ // Always verify before decrypting — never decrypt unauthenticated ciphertext
279
+ if (!constantTimeEqual(expectedTag, receivedTag)) {
280
+ wipe(expectedTag)
281
+ throw new Error('Authentication failed')
216
282
  }
283
+
284
+ const cbc2 = new SerpentCbc({ dangerUnauthenticated: true })
285
+ const pt = cbc2.decrypt(encKey, receivedIv, receivedCt)
286
+ cbc2.dispose()
287
+ wipe(expectedTag)
217
288
  ```
218
289
 
290
+ > [!NOTE]
291
+ > `Seal` with `SerpentCipher` does all of this for you — key derivation, IV handling, Encrypt-then-MAC, and constant-time verification — with no manual steps. The pattern above is only relevant if you need direct access to the raw `SerpentCbc` primitive.
292
+
219
293
  ---
220
294
 
221
295
  ### Generating random keys and nonces
@@ -223,9 +297,9 @@ if (!constantTimeEqual(computedMac, receivedMac)) {
223
297
  ```typescript
224
298
  import { randomBytes } from 'leviathan-crypto'
225
299
 
226
- const key = randomBytes(32) // 256-bit symmetric key
227
- const nonce = randomBytes(24) // 192-bit nonce for XChaCha20
228
- const iv = randomBytes(16) // 128-bit IV for Serpent-CBC
300
+ const key = randomBytes(32) // 256-bit symmetric key
301
+ const nonce = randomBytes(24) // 192-bit nonce for XChaCha20
302
+ const iv = randomBytes(16) // 128-bit IV for Serpent-CBC
229
303
  ```
230
304
 
231
305
  ---
@@ -272,6 +346,7 @@ console.log(combined.length) // 32
272
346
  | `hexToBytes` | Invalid hex characters | Bytes decode as `NaN` -> `0` |
273
347
  | `base64ToBytes` | Invalid length or characters | Returns `undefined` |
274
348
  | `constantTimeEqual` | Arrays differ in length | Returns `false` immediately |
349
+ | `constantTimeEqual` | Either array exceeds `CT_MAX_BYTES` | Throws `RangeError` |
275
350
  | `xor` | Arrays differ in length | Throws `RangeError` |
276
351
  | `randomBytes` | `crypto` not available | Throws (runtime-dependent) |
277
352
  | `hasSIMD` | `WebAssembly` not available | Returns `false` |
@@ -286,5 +361,7 @@ console.log(combined.length) // 32
286
361
  > - [chacha20](./chacha20.md) — ChaCha20/Poly1305 classes use `randomBytes` for nonce generation
287
362
  > - [sha2](./sha2.md) — SHA-2 and HMAC classes; output often converted with `bytesToHex`
288
363
  > - [sha3](./sha3.md) — SHA-3 and SHAKE classes; output often converted with `bytesToHex`
364
+ > - [argon2id](./argon2id.md) — passphrase-based encryption; uses `constantTimeEqual` for hash verification
365
+ > - [examples](./examples.md) — full HMAC-SHA256 custom protocol example using `constantTimeEqual`
289
366
  > - [types](./types.md) — public interfaces whose implementations rely on these utilities
290
367
  > - [test-suite](./test-suite.md) — test suite structure and vector corpus