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
@@ -0,0 +1,241 @@
1
+ # All Exports
2
+
3
+ > [!NOTE]
4
+ > Complete reference for every public export in leviathan-crypto, grouped by module. Follow the module links for deeper documentation on each class.
5
+
6
+ > ### Table of Contents
7
+ > - [Initialization](#initialization)
8
+ > - [Serpent-256](#serpent-256)
9
+ > - [Stream](#stream)
10
+ > - [Errors](#errors)
11
+ > - [XChaCha20 / Poly1305](#xchacha20--poly1305)
12
+ > - [SHA-2](#sha-2)
13
+ > - [SHA-3](#sha-3)
14
+ > - [Keccak (alias for SHA-3)](#keccak-alias-for-sha-3)
15
+ > - [ML-KEM (Post-quantum KEM)](#ml-kem-post-quantum-kem)
16
+ > - [Fortuna CSPRNG](#fortuna-csprng)
17
+ > - [Types](#types)
18
+ > - [Utilities](#utilities)
19
+
20
+ ---
21
+
22
+ ## Initialization
23
+
24
+ Root barrel `leviathan-crypto`. No module required.
25
+
26
+ | Export | Kind | Description |
27
+ |--------|------|-------------|
28
+ | `init` | function | Load and cache WASM modules. `init(sources: Partial<Record<Module, WasmSource>>)`. |
29
+ | `isInitialized` | function | `isInitialized(mod: Module): boolean`. Returns `true` if the given module has been loaded. Useful for diagnostic checks. |
30
+ | `Module` | type | `'serpent' \| 'chacha20' \| 'sha2' \| 'sha3' \| 'keccak' \| 'kyber'` |
31
+ | `WasmSource` | type | Union of all accepted WASM loading strategies. See below. |
32
+
33
+ **`WasmSource`** accepted by every init function:
34
+
35
+ | Value | Strategy |
36
+ |-------|----------|
37
+ | `string` | Decode gzip+base64 embedded blob |
38
+ | `URL` | `fetch` + `instantiateStreaming` |
39
+ | `ArrayBuffer` | Compile from raw WASM bytes |
40
+ | `Uint8Array` | Compile from raw WASM bytes |
41
+ | `WebAssembly.Module` | Instantiate pre-compiled module |
42
+ | `Response` | `instantiateStreaming` from fetch response |
43
+ | `Promise<Response>` | `instantiateStreaming` from deferred fetch |
44
+
45
+ See [init.md](./init.md) for full loading documentation.
46
+
47
+ ---
48
+
49
+ ## Serpent-256
50
+
51
+ Requires `init({ serpent: serpentWasm, sha2: sha2Wasm })` for authenticated classes, `init({ serpent: serpentWasm })` for raw modes.
52
+ Subpath: `leviathan-crypto/serpent`. See [serpent.md](./serpent.md).
53
+
54
+ | Export | Kind | Description |
55
+ |--------|------|-------------|
56
+ | `serpentInit` | function | Module-scoped init. `serpentInit(source: WasmSource)` loads only serpent. |
57
+ | `SerpentCipher` | const | `CipherSuite` for Serpent-256 CBC+HMAC-SHA-256. `keygen()` → 32-byte key. `formatEnum: 0x02`, `keySize: 32`, `tagSize: 32`, `padded: true`. Used with `Seal`, `SealStream`, `OpenStream`. |
58
+ | `Serpent` | class | Serpent-256 ECB block cipher. `loadKey()`, `encryptBlock()`, `decryptBlock()`. Unauthenticated. |
59
+ | `SerpentCtr` | class | Serpent-256 CTR mode. `beginEncrypt()`, `encryptChunk()`, `beginDecrypt()`, `decryptChunk()`. Unauthenticated. |
60
+ | `SerpentCbc` | class | Serpent-256 CBC mode with PKCS7 padding. `encrypt(key, iv, plaintext)`, `decrypt(key, iv, ciphertext)`. Unauthenticated. |
61
+
62
+ ---
63
+
64
+ ## Stream
65
+
66
+ Cipher-agnostic streaming encryption using the STREAM construction.
67
+ Subpath: `leviathan-crypto/stream`. See [aead.md](./aead.md).
68
+
69
+ | Export | Kind | Description |
70
+ |--------|------|-------------|
71
+ | `Seal` | class (static) | One-shot AEAD. `Seal.encrypt(suite, key, plaintext)` / `Seal.decrypt(suite, key, blob)`. Works with any `CipherSuite` including `KyberSuite`. Never instantiated. |
72
+ | `SealStream` | class | Cipher-agnostic streaming encryption (STREAM construction). `push(chunk)`, `finalize(chunk)`, `toTransformStream()`. |
73
+ | `OpenStream` | class | Cipher-agnostic streaming decryption. `pull(chunk)`, `finalize(chunk)`, `seek(index)`, `toTransformStream()`. |
74
+ | `SealStreamPool` | class | Parallel batch seal/open via Web Workers. `SealStreamPool.create(cipher, key, opts)` static factory. |
75
+ | `CipherSuite` | interface | Cipher-specific logic injected into SealStream/OpenStream. Implementations: `XChaCha20Cipher`, `SerpentCipher`, `KyberSuite`. See [ciphersuite.md](./ciphersuite.md). |
76
+ | `DerivedKeys` | interface | Opaque key material returned by `CipherSuite.deriveKeys()`. |
77
+ | `SealStreamOpts` | type | Options for SealStream: `chunkSize?`, `framed?`. |
78
+ | `PoolOpts` | type | Options for SealStreamPool: `wasm`, `workers?`, `chunkSize?`, `framed?`, `jobTimeout?`. |
79
+ | `HEADER_SIZE` | const | Stream header size in bytes (20). |
80
+ | `CHUNK_MIN` | const | Minimum chunk size (1024). |
81
+ | `CHUNK_MAX` | const | Maximum chunk size (16777215, u24 max). |
82
+ | `FLAG_FRAMED` | const | Header byte 0 framed flag (0x80). |
83
+ | `TAG_DATA` | const | Counter nonce final flag for data chunks (0x00). |
84
+ | `TAG_FINAL` | const | Counter nonce final flag for final chunk (0x01). |
85
+
86
+ ---
87
+
88
+ ## Errors
89
+
90
+ | Export | Kind | Description |
91
+ |--------|------|-------------|
92
+ | `AuthenticationError` | class | Thrown on AEAD auth failure. Extends `Error`. Constructor takes cipher name string. |
93
+
94
+ ---
95
+
96
+ ## XChaCha20 / Poly1305
97
+
98
+ Requires `init({ chacha20: chacha20Wasm })` or subpath `chacha20Init()`.
99
+ Subpath: `leviathan-crypto/chacha20`. See [chacha20.md](./chacha20.md).
100
+
101
+ | Export | Kind | Description |
102
+ |--------|------|-------------|
103
+ | `chacha20Init` | function | Module-scoped init. `chacha20Init(source: WasmSource)` loads only chacha20. |
104
+ | `XChaCha20Poly1305` | class | XChaCha20-Poly1305 AEAD. 24-byte nonce. `encrypt()` returns single `Uint8Array` (ct‖tag), `decrypt()` accepts same format. Single-use encrypt guard. |
105
+ | `XChaCha20Cipher` | const | `CipherSuite` for XChaCha20-Poly1305. `keygen()` → 32-byte key. `formatEnum: 0x01`, `keySize: 32`, `tagSize: 16`, `padded: false`. Used with `Seal`, `SealStream`, `OpenStream`. |
106
+ | `ChaCha20Poly1305` | class | ChaCha20-Poly1305 AEAD (RFC 8439). 12-byte nonce. `encrypt()` returns single `Uint8Array` (ct‖tag), `decrypt()` accepts same format. Single-use encrypt guard. |
107
+ | `ChaCha20` | class | ChaCha20 stream cipher (RFC 8439). `beginEncrypt()`, `encryptChunk()`. Unauthenticated. |
108
+ | `Poly1305` | class | Poly1305 one-time MAC (RFC 8439). `mac(key, msg)`. |
109
+
110
+ ---
111
+
112
+ ## SHA-2
113
+
114
+ Requires `init({ sha2: sha2Wasm })` or subpath `sha2Init(source)`.
115
+ Subpath: `leviathan-crypto/sha2`. See [sha2.md](./sha2.md).
116
+
117
+ | Export | Kind | Description |
118
+ |--------|------|-------------|
119
+ | `sha2Init` | function | Module-scoped init. `sha2Init(source: WasmSource)` loads only sha2. |
120
+ | `SHA256` | class | SHA-256 hash (FIPS 180-4). `hash(msg)` returns 32 bytes. |
121
+ | `SHA384` | class | SHA-384 hash (FIPS 180-4). `hash(msg)` returns 48 bytes. |
122
+ | `SHA512` | class | SHA-512 hash (FIPS 180-4). `hash(msg)` returns 64 bytes. |
123
+ | `HMAC_SHA256` | class | HMAC-SHA256 (RFC 2104). `hash(key, msg)` returns 32 bytes. |
124
+ | `HMAC_SHA384` | class | HMAC-SHA384 (RFC 2104). `hash(key, msg)` returns 48 bytes. |
125
+ | `HMAC_SHA512` | class | HMAC-SHA512 (RFC 2104). `hash(key, msg)` returns 64 bytes. |
126
+ | `HKDF_SHA256` | class | HKDF with HMAC-SHA256 (RFC 5869). `derive(ikm, salt, info, length)`. |
127
+ | `HKDF_SHA512` | class | HKDF with HMAC-SHA512 (RFC 5869). `derive(ikm, salt, info, length)`. |
128
+
129
+ ---
130
+
131
+ ## SHA-3
132
+
133
+ Requires `init({ sha3: sha3Wasm })` or subpath `sha3Init(source)`.
134
+ Subpath: `leviathan-crypto/sha3`. See [sha3.md](./sha3.md).
135
+
136
+ | Export | Kind | Description |
137
+ |--------|------|-------------|
138
+ | `sha3Init` | function | Module-scoped init. `sha3Init(source: WasmSource)` loads only sha3. |
139
+ | `SHA3_224` | class | SHA3-224 hash (FIPS 202). `hash(msg)` returns 28 bytes. |
140
+ | `SHA3_256` | class | SHA3-256 hash (FIPS 202). `hash(msg)` returns 32 bytes. |
141
+ | `SHA3_384` | class | SHA3-384 hash (FIPS 202). `hash(msg)` returns 48 bytes. |
142
+ | `SHA3_512` | class | SHA3-512 hash (FIPS 202). `hash(msg)` returns 64 bytes. |
143
+ | `SHAKE128` | class | SHAKE128 XOF (FIPS 202). Unbounded output. `hash(msg, outputLength)`, `absorb(msg)`, `squeeze(n)`, `reset()`. |
144
+ | `SHAKE256` | class | SHAKE256 XOF (FIPS 202). Unbounded output. `hash(msg, outputLength)`, `absorb(msg)`, `squeeze(n)`, `reset()`. |
145
+
146
+ ---
147
+
148
+ ## Keccak (alias for SHA-3)
149
+
150
+ `'keccak'` is an alias for `'sha3'`. Same WASM binary, same instance slot.
151
+ Both `init({ sha3: sha3Wasm })` and `init({ keccak: keccakWasm })` load the same module.
152
+ Provided so Kyber/ML-KEM consumers can use the semantically correct primitive name.
153
+ Subpath: `leviathan-crypto/keccak`.
154
+
155
+ | Export | Kind | Description |
156
+ |--------|------|-------------|
157
+ | `keccakInit` | function | Alias init. `keccakInit(source: WasmSource)` loads the sha3 WASM slot via the keccak alias. |
158
+ | `SHA3_224` | class | Re-exported from `leviathan-crypto/sha3`. |
159
+ | `SHA3_256` | class | Re-exported from `leviathan-crypto/sha3`. |
160
+ | `SHA3_384` | class | Re-exported from `leviathan-crypto/sha3`. |
161
+ | `SHA3_512` | class | Re-exported from `leviathan-crypto/sha3`. |
162
+ | `SHAKE128` | class | Re-exported from `leviathan-crypto/sha3`. |
163
+ | `SHAKE256` | class | Re-exported from `leviathan-crypto/sha3`. |
164
+
165
+ ---
166
+
167
+ ## ML-KEM (Post-quantum KEM)
168
+
169
+ Requires `init({ kyber: kyberWasm, sha3: sha3Wasm })`.
170
+ Subpath: `leviathan-crypto/kyber`. See [kyber.md](./kyber.md).
171
+
172
+ | Export | Kind | Description |
173
+ |--------|------|-------------|
174
+ | `kyberInit` | function | Module-scoped init. `kyberInit(source: WasmSource)` loads only kyber WASM. |
175
+ | `MlKemBase` | class | Abstract base class for all ML-KEM variants. Holds `params: KyberParams`. Not normally instantiated directly. Use `MlKem512`, `MlKem768`, or `MlKem1024`. |
176
+ | `MlKem512` | class | ML-KEM-512. k=2, η₁=3. `keygen()`, `encapsulate(ek)`, `decapsulate(dk, c)`, `checkEncapsulationKey(ek)`, `checkDecapsulationKey(dk)`. |
177
+ | `MlKem768` | class | ML-KEM-768. k=3, η₁=2. Recommended default. Same API as MlKem512. |
178
+ | `MlKem1024` | class | ML-KEM-1024. k=4, η₁=2. Same API as MlKem512. |
179
+ | `KyberSuite` | function | Factory. `KyberSuite(kem, innerCipher)` → `CipherSuite & { keygen(): KyberKeyPair }`. Wraps `MlKemBase` + `CipherSuite` into a hybrid KEM+AEAD suite for use with `Seal`, `SealStream`, `OpenStream`. |
180
+ | `KyberKeyPair` | type | `{ encapsulationKey: Uint8Array, decapsulationKey: Uint8Array }` |
181
+ | `KyberEncapsulation` | type | `{ ciphertext: Uint8Array, sharedSecret: Uint8Array }` |
182
+ | `KyberParams` | type | Parameter set configuration (k, η₁, η₂, dᵤ, dᵥ, byte sizes). |
183
+ | `MLKEM512` | const | Parameter set for ML-KEM-512. |
184
+ | `MLKEM768` | const | Parameter set for ML-KEM-768. |
185
+ | `MLKEM1024` | const | Parameter set for ML-KEM-1024. |
186
+
187
+ > [!NOTE]
188
+ > `ntt_scalar` and `invntt_scalar` are scalar NTT references exported for SIMD gate tests. They are not part of the public API.
189
+
190
+ ---
191
+
192
+ ## Fortuna CSPRNG
193
+
194
+ Requires `init({ serpent: serpentWasm, sha2: sha2Wasm })`. See [fortuna.md](./fortuna.md).
195
+
196
+ | Export | Kind | Description |
197
+ |--------|------|-------------|
198
+ | `Fortuna` | class | Fortuna CSPRNG (Ferguson & Schneier). `Fortuna.create()` static factory, `get(n)`, `addEntropy()`, `stop()`. |
199
+
200
+ ---
201
+
202
+ ## Types
203
+
204
+ No `init()` required. See [types.md](./types.md).
205
+
206
+ | Export | Kind | Description |
207
+ |--------|------|-------------|
208
+ | `Hash` | interface | `hash(msg): Uint8Array`, `dispose()` |
209
+ | `KeyedHash` | interface | `hash(key, msg): Uint8Array`, `dispose()` |
210
+ | `Blockcipher` | interface | `encrypt(block): Uint8Array`, `decrypt(block): Uint8Array`, `dispose()` |
211
+ | `Streamcipher` | interface | `encrypt(msg): Uint8Array`, `decrypt(msg): Uint8Array`, `dispose()` |
212
+ | `AEAD` | interface | `encrypt(msg, aad?): Uint8Array`, `decrypt(ciphertext, aad?): Uint8Array`, `dispose()` |
213
+
214
+ ---
215
+
216
+ ## Utilities
217
+
218
+ No `init()` required. See [utils.md](./utils.md).
219
+
220
+ | Export | Kind | Description |
221
+ |--------|------|-------------|
222
+ | `hexToBytes` | function | Hex string to `Uint8Array`. Accepts `0x` prefix, uppercase/lowercase. Throws `RangeError` on odd-length input. |
223
+ | `bytesToHex` | function | `Uint8Array` to lowercase hex string. |
224
+ | `utf8ToBytes` | function | UTF-8 string to `Uint8Array`. |
225
+ | `bytesToUtf8` | function | `Uint8Array` to UTF-8 string. |
226
+ | `base64ToBytes` | function | Base64/base64url string to `Uint8Array`. Returns `undefined` on invalid input. |
227
+ | `bytesToBase64` | function | `Uint8Array` to base64 string. Pass `url=true` for base64url. |
228
+ | `constantTimeEqual` | function | Best-available constant-time byte-array equality. Uses WASM SIMD when available to eliminate JIT timing leaks; falls back to XOR-accumulate in JS. Returns `false` immediately on length mismatch. Throws `RangeError` if either input exceeds `CT_MAX_BYTES`. |
229
+ | `CT_MAX_BYTES` | const | Maximum input size for `constantTimeEqual` per side (32768 bytes, one 64 KiB WASM page split between two buffers). |
230
+ | `wipe` | function | Zero a typed array in place. |
231
+ | `xor` | function | XOR two equal-length `Uint8Array`s, returns new array. |
232
+ | `concat` | function | Concatenate one or more `Uint8Array`s into a new array. Variadic. |
233
+ | `randomBytes` | function | Cryptographically secure random bytes via Web Crypto API. |
234
+ | `hasSIMD` | function | Returns `true` if the runtime supports WebAssembly SIMD. Cached after first call. Used internally for CTR/CBC-decrypt and ChaCha20 dispatch. Exported for informational use. |
235
+
236
+ ---
237
+
238
+ > ## Cross-References
239
+ >
240
+ > - [index](./README.md) — Project Documentation index
241
+ > - [architecture](./architecture.md) — architecture overview, module relationships, buffer layouts, and build pipeline
@@ -1,30 +1,37 @@
1
- # Fortuna: Cryptographically Secure Pseudorandom Number Generator (CSPRNG)
1
+ # Fortuna CSPRNG
2
2
 
3
3
  > [!NOTE]
4
4
  > A CSPRNG that continuously collects entropy from the environment and generates
5
5
  > cryptographically secure random bytes, backed by WASM Serpent-256 and SHA-256.
6
6
 
7
+ > ### Table of Contents
8
+ > - [Overview](#overview)
9
+ > - [Security Notes](#security-notes)
10
+ > - [API Reference](#api-reference)
11
+ > - [Usage Examples](#usage-examples)
12
+ > - [Error Conditions](#error-conditions)
13
+ > - [How It Works (Simplified)](#how-it-works-simplified)
14
+
15
+ ---
16
+
7
17
  ## Overview
8
18
 
9
19
  A cryptographically secure pseudorandom number generator (CSPRNG) produces random
10
20
  bytes that are indistinguishable from true randomness to any observer, even one
11
21
  with significant computational resources. This matters because many security
12
- operations -- generating encryption keys, initialization vectors, nonces, tokens
13
- -- require randomness that an attacker cannot predict. If an attacker can predict
22
+ operations require unpredictable randomness: generating encryption keys, initialization vectors, nonces, and tokens. If an attacker can predict
14
23
  the output of your random number generator, they can predict your keys, and your
15
24
  encryption provides no protection.
16
25
 
17
26
  Fortuna is a CSPRNG designed by Bruce Schneier and Niels Ferguson, published in
18
27
  *Practical Cryptography* (2003). It continuously collects entropy from multiple
19
- sources -- mouse movements, keyboard events, system timers, OS randomness -- and
28
+ sources (mouse movements, keyboard events, system timers, OS randomness) and
20
29
  feeds that entropy into 32 independent pools. When you request random bytes,
21
30
  Fortuna combines pool contents and uses them to reseed an internal generator
22
31
  built on Serpent-256 (block cipher) and SHA-256 (hash function). Both primitives
23
32
  run entirely in WebAssembly.
24
33
 
25
- Why use Fortuna instead of `crypto.getRandomValues()`? The OS random source is
26
- good, and Fortuna seeds itself from it on creation. But Fortuna adds two
27
- properties on top. First, **forward secrecy**: after every call to `get()`, the
34
+ Fortuna adds two properties on top of `crypto.getRandomValues()`. First, **forward secrecy**: after every call to `get()`, the
28
35
  internal generation key is replaced, so compromising the current state does not
29
36
  reveal any past outputs. Second, **defense-in-depth entropy pooling**: Fortuna
30
37
  collects entropy from many independent sources and distributes it across 32 pools
@@ -39,41 +46,19 @@ you must use the `Fortuna.create()` static factory rather than `new Fortuna()`.
39
46
 
40
47
  ## Security Notes
41
48
 
42
- - **Forward secrecy** -- The generation key is replaced after every call to
43
- `get()`. If an attacker compromises the internal state at time T, they cannot
44
- reconstruct any output produced before time T.
45
-
46
- - **32 entropy pools** -- Entropy is distributed across 32 independent pools
47
- using round-robin assignment. Pool 0 is used on every reseed, pool 1 on every
48
- second reseed, pool 2 on every fourth, and so on. This exponential schedule
49
- means that even if an attacker can observe or influence some entropy sources,
50
- higher-numbered pools accumulate enough entropy over time to produce a strong
51
- reseed eventually.
52
-
53
- - **Immediate usability** -- Fortuna seeds itself from `crypto.getRandomValues()`
54
- (browser) or `crypto.randomBytes()` (Node.js) during creation, so it is
55
- immediately usable. You do not need to wait for entropy to accumulate before
56
- calling `get()`.
57
-
58
- - **Browser entropy sources** -- mouse movements, keyboard events, click events,
59
- scroll position, touch events, device motion and orientation,
60
- `performance.now()` timing, DOM content hash, and periodic
61
- `crypto.getRandomValues()`.
62
-
63
- - **Node.js entropy sources** -- `crypto.randomBytes()`, `process.hrtime` (nanosecond
64
- timing jitter), `process.cpuUsage()`, `process.memoryUsage()`, `os.loadavg()`,
65
- `os.freemem()`.
66
-
67
- - **Wipe state when done** -- Call `stop()` when you are finished with the
68
- instance. This wipes the generation key and counter from memory and stops all
69
- background entropy collectors. Key material should not persist longer than
70
- necessary.
71
-
72
- - **Output quality depends on entropy** -- The initial seed from the OS random
73
- source is strong. Over time, the additional entropy collectors improve the
74
- state further. In environments with limited user interaction (headless servers,
75
- automated tests), fewer entropy sources contribute, but the OS random seed
76
- still provides a solid baseline.
49
+ **Forward secrecy.** The generation key is replaced after every call to `get()`. If an attacker compromises the internal state at time T, they cannot reconstruct any output produced before time T.
50
+
51
+ **32 entropy pools.** Entropy is distributed across 32 independent pools using round-robin assignment. Pool 0 is used on every reseed, pool 1 on every second reseed, pool 2 on every fourth, and so on. This exponential schedule means that even if an attacker can observe or influence some entropy sources, higher-numbered pools accumulate enough entropy over time to produce a strong reseed eventually.
52
+
53
+ **Immediate usability.** Fortuna seeds itself from `crypto.getRandomValues()` (browser) or `crypto.randomBytes()` (Node.js) during creation. You do not need to wait for entropy to accumulate before calling `get()`.
54
+
55
+ **Browser entropy sources.** Mouse movements, keyboard events, click events, scroll position, touch events, device motion and orientation, `performance.now()` timing, DOM content hash, and periodic `crypto.getRandomValues()`.
56
+
57
+ **Node.js entropy sources.** `crypto.randomBytes()`, `process.hrtime` (nanosecond timing jitter), `process.cpuUsage()`, `process.memoryUsage()`, `os.loadavg()`, `os.freemem()`.
58
+
59
+ **Wipe state when done.** Call `stop()` when you are finished with the instance. This wipes the generation key and counter from memory and stops all background entropy collectors. Key material should not persist longer than necessary.
60
+
61
+ **Output quality depends on entropy.** The initial seed from the OS random source is strong. Over time the additional entropy collectors improve the state further. In environments with limited user interaction (headless servers, automated tests), fewer entropy sources contribute, but the OS random seed still provides a solid baseline.
77
62
 
78
63
  ---
79
64
 
@@ -82,7 +67,7 @@ you must use the `Fortuna.create()` static factory rather than `new Fortuna()`.
82
67
  ### `Fortuna.create(opts?)`
83
68
 
84
69
  Static async factory. Returns a `Promise<Fortuna>`. The returned instance is
85
- guaranteed to be seeded -- `create()` forces an initial reseed before resolving,
70
+ guaranteed to be seeded. `create()` forces an initial reseed before resolving,
86
71
  so `get()` is immediately usable.
87
72
 
88
73
  ```typescript
@@ -95,12 +80,11 @@ static async create(opts?: {
95
80
  | Parameter | Type | Default | Description |
96
81
  |-----------|------|---------|-------------|
97
82
  | `opts.msPerReseed` | `number` | `100` | Minimum milliseconds between reseeds. |
98
- | `opts.entropy` | `Uint8Array` | -- | Optional extra entropy to mix in during creation. |
83
+ | `opts.entropy` | `Uint8Array` | | Optional extra entropy to mix in during creation. |
99
84
 
100
- Throws if `init(['serpent', 'sha2'])` has not been called.
85
+ Throws if `init({ serpent: serpentWasm, sha2: sha2Wasm })` has not been called.
101
86
 
102
- Direct construction with `new Fortuna()` is not possible -- the constructor is
103
- private. Always use `Fortuna.create()`.
87
+ Direct construction with `new Fortuna()` is not possible. The constructor is private. Always use `Fortuna.create()`.
104
88
 
105
89
  ---
106
90
 
@@ -116,7 +100,7 @@ Returns a `Uint8Array` of the requested length. The instance is always seeded
116
100
  after `create()` resolves, so this method is guaranteed to return data.
117
101
 
118
102
  After producing the output, the generation key is replaced with fresh
119
- pseudorandom material. This is the forward secrecy mechanism -- the key used to
103
+ pseudorandom material. This is the forward secrecy mechanism. The key used to
120
104
  produce this output no longer exists.
121
105
 
122
106
  ---
@@ -144,8 +128,7 @@ getEntropy(): number
144
128
  ```
145
129
 
146
130
  Returns the estimated total entropy accumulated across all pools, in bytes. This
147
- is an estimate, not a guarantee -- it reflects the sum of entropy credits assigned
148
- by each collector.
131
+ is an estimate, not a guarantee. It reflects the sum of entropy credits assigned by each collector.
149
132
 
150
133
  ---
151
134
 
@@ -180,13 +163,15 @@ There is no `start()` or restart capability.
180
163
 
181
164
  ## Usage Examples
182
165
 
183
- ### Basic usage -- generate random bytes
166
+ ### Basic usage
184
167
 
185
168
  ```typescript
186
169
  import { init, Fortuna } from 'leviathan-crypto'
170
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
171
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
187
172
 
188
173
  // Initialize both WASM modules that Fortuna depends on
189
- await init(['serpent', 'sha2'])
174
+ await init({ serpent: serpentWasm, sha2: sha2Wasm })
190
175
 
191
176
  // Create the CSPRNG
192
177
  const rng = await Fortuna.create()
@@ -197,7 +182,7 @@ const key = rng.get(32)
197
182
  // Generate 12 random bytes (e.g., for a nonce)
198
183
  const nonce = rng.get(12)
199
184
 
200
- // Clean up when done -- wipes key material from memory
185
+ // Clean up when done, wipes key material from memory
201
186
  rng.stop()
202
187
  ```
203
188
 
@@ -205,8 +190,10 @@ rng.stop()
205
190
 
206
191
  ```typescript
207
192
  import { init, Fortuna, utf8ToBytes } from 'leviathan-crypto'
193
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
194
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
208
195
 
209
- await init(['serpent', 'sha2'])
196
+ await init({ serpent: serpentWasm, sha2: sha2Wasm })
210
197
  const rng = await Fortuna.create()
211
198
 
212
199
  // Feed application-specific data as additional entropy.
@@ -227,8 +214,10 @@ rng.stop()
227
214
 
228
215
  ```typescript
229
216
  import { init, Fortuna } from 'leviathan-crypto'
217
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
218
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
230
219
 
231
- await init(['serpent', 'sha2'])
220
+ await init({ serpent: serpentWasm, sha2: sha2Wasm })
232
221
 
233
222
  // Fortuna automatically registers browser event listeners on creation:
234
223
  // - mousemove (throttled to 50ms)
@@ -239,7 +228,7 @@ await init(['serpent', 'sha2'])
239
228
  // - devicemotion, deviceorientation, orientationchange
240
229
  //
241
230
  // Every user interaction feeds entropy into the pools.
242
- // No manual setup is needed -- it starts collecting immediately.
231
+ // No manual setup is needed, it starts collecting immediately.
243
232
 
244
233
  const rng = await Fortuna.create()
245
234
 
@@ -259,8 +248,10 @@ window.addEventListener('beforeunload', () => rng.stop())
259
248
 
260
249
  ```typescript
261
250
  import { init, Fortuna } from 'leviathan-crypto'
251
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
252
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
262
253
 
263
- await init(['serpent', 'sha2'])
254
+ await init({ serpent: serpentWasm, sha2: sha2Wasm })
264
255
 
265
256
  // You can pass extra entropy at creation time.
266
257
  // This is mixed into the pools during initialization, before the
@@ -279,9 +270,9 @@ rng.stop()
279
270
 
280
271
  | Condition | What happens |
281
272
  |-----------|-------------|
282
- | `init()` not called | `Fortuna.create()` throws: `leviathan-crypto: call init(['serpent', 'sha2']) before using Fortuna` |
283
- | Only one module initialized | Same error -- both `serpent` and `sha2` must be initialized. |
284
- | `new Fortuna()` | Compile-time error -- the constructor is private. TypeScript will not allow it. |
273
+ | `init()` not called | `Fortuna.create()` throws: `leviathan-crypto: call init({ serpent: ..., sha2: ... }) before using Fortuna` |
274
+ | Only one module initialized | Same error. Both `serpent` and `sha2` must be initialized. |
275
+ | `new Fortuna()` | Compile-time error. The constructor is private. TypeScript will not allow it. |
285
276
  | Any method after `stop()` | Throws: `Fortuna instance has been disposed`. The instance is permanently disposed. |
286
277
 
287
278
  ---
@@ -291,21 +282,21 @@ rng.stop()
291
282
  For readers who want to understand what Fortuna does internally, without needing
292
283
  to read the spec:
293
284
 
294
- 1. **Entropy collection** -- Background listeners and timers capture small,
285
+ 1. **Entropy collection.** Background listeners and timers capture small,
295
286
  unpredictable measurements (mouse coordinates, nanosecond timings, memory
296
287
  usage) and feed them into 32 separate pools via SHA-256 hash chaining.
297
288
 
298
- 2. **Reseed** -- When pool 0 has accumulated enough entropy and enough time has
289
+ 2. **Reseed.** When pool 0 has accumulated enough entropy and enough time has
299
290
  passed since the last reseed, Fortuna combines the contents of eligible pools
300
291
  (determined by the reseed counter) into a seed, and derives a new generation
301
292
  key: `genKey = SHA-256(genKey || seed)`.
302
293
 
303
- 3. **Generation** -- To produce output, the generator encrypts an incrementing
294
+ 3. **Generation.** To produce output, the generator encrypts an incrementing
304
295
  counter with Serpent-256 in ECB mode using the current generation key. The
305
296
  output is the concatenation of encrypted counter blocks, truncated to the
306
297
  requested length.
307
298
 
308
- 4. **Key replacement** -- Immediately after producing output, the generation key
299
+ 4. **Key replacement.** Immediately after producing output, the generation key
309
300
  is replaced with fresh pseudorandom blocks. The old key is gone. This is what
310
301
  provides forward secrecy.
311
302
 
@@ -315,8 +306,8 @@ to read the spec:
315
306
  >
316
307
  > - [index](./README.md) — Project Documentation index
317
308
  > - [architecture](./architecture.md) — architecture overview, module relationships, buffer layouts, and build pipeline
318
- > - [serpent](./serpent.md): Serpent-256 TypeScript API (Fortuna uses Serpent ECB internally)
319
- > - [sha2](./sha2.md): SHA-256 TypeScript API (Fortuna uses SHA-256 for entropy accumulation)
320
- > - [asm_serpent](./asm_serpent.md): Serpent-256 WASM implementation details
321
- > - [asm_sha2](./asm_sha2.md): SHA-256 WASM implementation details
322
- > - [utils](./utils.md): `randomBytes()` for simpler random generation needs
309
+ > - [serpent](./serpent.md) Serpent-256 TypeScript API (Fortuna uses Serpent ECB internally)
310
+ > - [sha2](./sha2.md) SHA-256 TypeScript API (Fortuna uses SHA-256 for entropy accumulation)
311
+ > - [asm_serpent](./asm_serpent.md) Serpent-256 WASM implementation details
312
+ > - [asm_sha2](./asm_sha2.md) SHA-256 WASM implementation details
313
+ > - [utils](./utils.md) `randomBytes()` for simpler random generation needs