leviathan-crypto 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CLAUDE.md +171 -7
  2. package/LICENSE +4 -0
  3. package/README.md +109 -54
  4. package/SECURITY.md +125 -233
  5. package/dist/chacha20/cipher-suite.d.ts +10 -0
  6. package/dist/chacha20/cipher-suite.js +66 -2
  7. package/dist/chacha20/generator.d.ts +12 -0
  8. package/dist/chacha20/generator.js +91 -0
  9. package/dist/chacha20/index.d.ts +97 -1
  10. package/dist/chacha20/index.js +139 -11
  11. package/dist/chacha20/ops.d.ts +57 -6
  12. package/dist/chacha20/ops.js +93 -13
  13. package/dist/chacha20/pool-worker.js +12 -0
  14. package/dist/chacha20/types.d.ts +1 -32
  15. package/dist/ct-wasm.js +1 -1
  16. package/dist/ct.wasm +0 -0
  17. package/dist/docs/aead.md +69 -26
  18. package/dist/docs/architecture.md +600 -520
  19. package/dist/docs/argon2id.md +17 -14
  20. package/dist/docs/chacha20.md +146 -39
  21. package/dist/docs/exports.md +46 -10
  22. package/dist/docs/fortuna.md +339 -122
  23. package/dist/docs/init.md +24 -25
  24. package/dist/docs/loader.md +142 -47
  25. package/dist/docs/serpent.md +139 -41
  26. package/dist/docs/sha2.md +77 -19
  27. package/dist/docs/sha3.md +81 -15
  28. package/dist/docs/types.md +156 -15
  29. package/dist/docs/utils.md +171 -81
  30. package/dist/embedded/chacha20-pool-worker.d.ts +1 -0
  31. package/dist/embedded/chacha20-pool-worker.js +5 -0
  32. package/dist/embedded/kyber.d.ts +1 -1
  33. package/dist/embedded/kyber.js +1 -1
  34. package/dist/embedded/serpent-pool-worker.d.ts +1 -0
  35. package/dist/embedded/serpent-pool-worker.js +5 -0
  36. package/dist/embedded/serpent.d.ts +1 -1
  37. package/dist/embedded/serpent.js +1 -1
  38. package/dist/fortuna.d.ts +14 -8
  39. package/dist/fortuna.js +144 -50
  40. package/dist/index.d.ts +8 -6
  41. package/dist/index.js +6 -5
  42. package/dist/init.d.ts +0 -2
  43. package/dist/init.js +83 -3
  44. package/dist/kyber/indcpa.js +4 -4
  45. package/dist/kyber/index.js +25 -5
  46. package/dist/kyber/kem.js +56 -1
  47. package/dist/kyber/suite.d.ts +1 -2
  48. package/dist/kyber/suite.js +1 -0
  49. package/dist/kyber/types.d.ts +1 -0
  50. package/dist/kyber/validate.d.ts +8 -4
  51. package/dist/kyber/validate.js +18 -14
  52. package/dist/kyber.wasm +0 -0
  53. package/dist/loader.d.ts +7 -2
  54. package/dist/loader.js +25 -28
  55. package/dist/ratchet/index.d.ts +6 -0
  56. package/dist/ratchet/index.js +37 -0
  57. package/dist/ratchet/kdf-chain.d.ts +13 -0
  58. package/dist/ratchet/kdf-chain.js +85 -0
  59. package/dist/ratchet/ratchet-keypair.d.ts +9 -0
  60. package/dist/ratchet/ratchet-keypair.js +61 -0
  61. package/dist/ratchet/root-kdf.d.ts +4 -0
  62. package/dist/ratchet/root-kdf.js +124 -0
  63. package/dist/ratchet/skipped-key-store.d.ts +14 -0
  64. package/dist/ratchet/skipped-key-store.js +154 -0
  65. package/dist/ratchet/types.d.ts +36 -0
  66. package/dist/ratchet/types.js +26 -0
  67. package/dist/serpent/cipher-suite.d.ts +10 -0
  68. package/dist/serpent/cipher-suite.js +136 -50
  69. package/dist/serpent/generator.d.ts +12 -0
  70. package/dist/serpent/generator.js +97 -0
  71. package/dist/serpent/index.d.ts +61 -1
  72. package/dist/serpent/index.js +92 -7
  73. package/dist/serpent/pool-worker.js +25 -95
  74. package/dist/serpent/serpent-cbc.d.ts +14 -4
  75. package/dist/serpent/serpent-cbc.js +58 -34
  76. package/dist/serpent/shared-ops.d.ts +83 -0
  77. package/dist/serpent/shared-ops.js +213 -0
  78. package/dist/serpent/types.d.ts +1 -5
  79. package/dist/serpent.wasm +0 -0
  80. package/dist/sha2/hash.d.ts +2 -0
  81. package/dist/sha2/hash.js +53 -0
  82. package/dist/sha2/index.d.ts +1 -0
  83. package/dist/sha2/index.js +15 -1
  84. package/dist/sha3/hash.d.ts +2 -0
  85. package/dist/sha3/hash.js +53 -0
  86. package/dist/sha3/index.d.ts +17 -2
  87. package/dist/sha3/index.js +79 -7
  88. package/dist/stream/header.js +5 -5
  89. package/dist/stream/open-stream.js +36 -14
  90. package/dist/stream/seal-stream-pool.d.ts +1 -0
  91. package/dist/stream/seal-stream-pool.js +47 -8
  92. package/dist/stream/seal-stream.js +29 -11
  93. package/dist/stream/types.d.ts +1 -0
  94. package/dist/types.d.ts +21 -0
  95. package/dist/utils.d.ts +7 -8
  96. package/dist/utils.js +73 -40
  97. package/dist/wasm-source.d.ts +9 -8
  98. package/package.json +79 -64
@@ -1,13 +1,15 @@
1
- # Serpent-256 TypeScript API
1
+ <img src="https://github.com/xero/leviathan-crypto/raw/main/docs/logo.svg" alt="logo" width="120" align="left" margin="10">
2
2
 
3
- > [!NOTE]
4
- > See [Serpent implementation audit](./serpent_audit.md) for algorithm correctness verifications.
3
+ ### Serpent-256 TypeScript API
4
+
5
+ See [Serpent implementation audit](./serpent_audit.md) for algorithm correctness verifications.
5
6
 
6
7
  > ### Table of Contents
7
8
  > - [Overview](#overview)
8
9
  > - [Security Notes](#security-notes)
9
10
  > - [Module Init](#module-init)
10
11
  > - [API Reference](#api-reference)
12
+ > - [SerpentGenerator](#serpentgenerator)
11
13
  > - [Usage Examples](#usage-examples)
12
14
  > - [Error Conditions](#error-conditions)
13
15
 
@@ -44,12 +46,10 @@ corrupted plaintext on decryption. Decryption succeeds without any indication of
44
46
  tampering. There is no integrity check. Your caller receives garbage and has no way
45
47
  to distinguish it from the original message.
46
48
 
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.
50
-
51
- `Seal` with `XChaCha20Cipher` is an alternative using a different cipher.
52
- See [chacha20.md](./chacha20.md).
49
+ [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher)
50
+ eliminates this problem. It computes an HMAC tag over the ciphertext and
51
+ verifies it before decryption. If anything has been modified, `Seal.decrypt()`
52
+ throws instead of returning corrupted data.
53
53
 
54
54
  ### Never reuse a nonce or IV with the same key
55
55
 
@@ -58,13 +58,13 @@ same keystream, which means an attacker can XOR two ciphertexts together and
58
58
  recover both plaintexts. Always generate a fresh random nonce for each message.
59
59
  In CBC mode, the IV must be random and unpredictable for each encryption. A predictable IV enables chosen-plaintext attacks.
60
60
 
61
- Use `randomBytes(16)` to generate nonces and IVs. `Seal` with `SerpentCipher` handles IV generation internally.
61
+ Use `randomBytes(16)` to generate nonces and IVs. [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher) handles IV generation internally.
62
62
 
63
63
  ### Always use 256-bit keys
64
64
 
65
65
  Unless you have a specific reason to use a shorter key, pass a 32-byte key to
66
66
  every Serpent operation. Shorter keys provide less security margin and there is no
67
- meaningful performance benefit to using them. `SerpentCipher` requires a 32-byte key; HKDF derives enc/mac/iv keys internally.
67
+ meaningful performance benefit to using them. [`SerpentCipher`](./ciphersuite.md#serpentcipher) requires a 32-byte key; HKDF derives enc/mac/iv keys internally.
68
68
 
69
69
  ### Call dispose() when done
70
70
 
@@ -127,7 +127,7 @@ Requires `init({ serpent: serpentWasm, sha2: sha2Wasm })`.
127
127
 
128
128
  #### `SerpentCipher.keygen(): Uint8Array`
129
129
 
130
- Returns `randomBytes(32)`. Convenience method. Not on the `CipherSuite` interface.
130
+ Returns `randomBytes(32)`. Convenience method. Not on the [`CipherSuite`](./ciphersuite.md) interface.
131
131
 
132
132
  #### Usage with `Seal`
133
133
 
@@ -166,7 +166,7 @@ See [aead.md](./aead.md) for the full `Seal`, `SealStream`, and `OpenStream` API
166
166
  ### Serpent
167
167
 
168
168
  Raw Serpent block encryption and decryption. Operates on exactly 16-byte blocks.
169
- This class is a low-level building block; use `Seal` with `SerpentCipher` for most purposes.
169
+ This class is a low-level building block
170
170
 
171
171
  ```typescript
172
172
  class Serpent {
@@ -223,9 +223,17 @@ stream of chunks.
223
223
 
224
224
  > [!WARNING]
225
225
  > CTR mode is unauthenticated. An attacker can modify ciphertext
226
- > without detection. Use `Seal` with `SerpentCipher` for authenticated encryption, or pair
226
+ > without detection. Use [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher) for authenticated encryption, or pair
227
227
  > with HMAC-SHA256 (Encrypt-then-MAC).
228
228
 
229
+ > [!CAUTION]
230
+ > `SerpentCtr` is stateful and holds exclusive access to the `serpent` WASM
231
+ > module for its entire lifetime. Constructing a second `SerpentCtr`/
232
+ > `SerpentCbc`, `SerpentCipher` usage (`Seal.encrypt(SerpentCipher, ...)`,
233
+ > `SealStream` with `SerpentCipher`), or any atomic serpent class
234
+ > (`Serpent` block) while this instance is live throws. Call `dispose()`
235
+ > when done. Pool workers are unaffected.
236
+
229
237
  ```typescript
230
238
  class SerpentCtr {
231
239
  constructor(opts: { dangerUnauthenticated: true })
@@ -268,7 +276,7 @@ automatically.
268
276
  - **chunk**: any length up to the module's internal chunk buffer size. Throws `RangeError` if the chunk exceeds the maximum size.
269
277
 
270
278
  > [!NOTE]
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
+ > Always uses the 4-wide SIMD path (`encryptChunk_simd`). SIMD is required by the serpent module; `init()` throws on runtimes without WebAssembly SIMD support.
272
280
 
273
281
  ---
274
282
 
@@ -294,6 +302,11 @@ Functionally identical to `encryptChunk()`.
294
302
 
295
303
  Wipes all key material and intermediate state from WASM memory.
296
304
 
305
+ After `dispose()`, all instance methods (`beginEncrypt`, `encryptChunk`,
306
+ `beginDecrypt`, `decryptChunk`) throw `Error: SerpentCtr: instance has been
307
+ disposed`. Disposal is permanent; construct a new instance if you need to
308
+ continue.
309
+
297
310
  ---
298
311
 
299
312
  ### SerpentCbc
@@ -303,7 +316,14 @@ Encrypts and decrypts entire messages in a single call.
303
316
 
304
317
  > [!WARNING]
305
318
  > CBC mode is unauthenticated. Always authenticate the output with
306
- > HMAC-SHA256 (Encrypt-then-MAC) or use `Seal` with `SerpentCipher` instead.
319
+ > HMAC-SHA256 (Encrypt-then-MAC) or use [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher) instead.
320
+
321
+ > [!CAUTION]
322
+ > `SerpentCbc` is stateful and holds exclusive access to the `serpent` WASM
323
+ > module for its entire lifetime. Constructing a second `SerpentCbc`/
324
+ > `SerpentCtr`, `SerpentCipher` usage (which internally constructs a
325
+ > `SerpentCbc`), or any atomic serpent class (`Serpent` block) while this
326
+ > instance is live throws. Call `dispose()` when done.
307
327
 
308
328
  ```typescript
309
329
  class SerpentCbc {
@@ -346,12 +366,12 @@ Decrypts Serpent CBC ciphertext and strips PKCS7 padding.
346
366
 
347
367
  - **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
348
368
  - **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.
369
+ - **ciphertext**: must be a non-zero multiple of 16 bytes. Throws `RangeError` with the generic message `'invalid ciphertext'` on any failure — zero length, non-multiple-of-16 length, or invalid PKCS7 padding. The single message and branch-free padding check close the Vaudenay 2002 padding-oracle surface; a caller cannot distinguish failure modes by message or by timing.
350
370
 
351
371
  Returns the decrypted plaintext as a new `Uint8Array`.
352
372
 
353
373
  > [!NOTE]
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.
374
+ > Decryption always uses the 4-wide SIMD path (`cbcDecryptChunk_simd`). SIMD is required by the serpent module; `init()` throws on runtimes without it. CBC encryption has no SIMD variant because each ciphertext block depends on the previous one.
355
375
 
356
376
  ---
357
377
 
@@ -359,6 +379,81 @@ Returns the decrypted plaintext as a new `Uint8Array`.
359
379
 
360
380
  Wipes all key material and intermediate state from WASM memory.
361
381
 
382
+ After `dispose()`, `encrypt` and `decrypt` throw `Error: SerpentCbc: instance
383
+ has been disposed`. Disposal is permanent; construct a new instance if you
384
+ need to continue.
385
+
386
+ ---
387
+
388
+ ### Security — direct use of `SerpentCbc`
389
+
390
+ `SerpentCbc` is unauthenticated. If you use it directly via
391
+ `{ dangerUnauthenticated: true }`, you are responsible for:
392
+
393
+ 1. Authenticating the ciphertext (HMAC-SHA256 in Encrypt-then-MAC order)
394
+ 2. Verifying the HMAC **before** calling `decrypt()`
395
+ 3. Using a unique, random IV per (key, message)
396
+
397
+ `SerpentCbc.decrypt()` throws a single generic `'invalid ciphertext'`
398
+ error for all padding failures and runs its validation in constant time over the
399
+ final 16 bytes. This mitigates padding-oracle attacks (Vaudenay 2002) on callers
400
+ that surface errors to remote parties. The authenticated composition
401
+ `SerpentCipher` always verifies HMAC before any PKCS7 processing and is the
402
+ recommended path.
403
+
404
+ ---
405
+
406
+ ## SerpentGenerator
407
+
408
+ Serpent-256 ECB counter-mode PRF for Fortuna's generator slot. Implements the
409
+ `Generator` interface (Practical Cryptography, Ferguson & Schneier 2003 §9.4).
410
+ This is a plain `const` object, not a class — no instantiation, no `dispose()`.
411
+
412
+ Requires `init({ serpent: serpentWasm })`. See [fortuna.md](./fortuna.md) for
413
+ full usage with `Fortuna.create()`.
414
+
415
+ | Property | Value |
416
+ |----------|-------|
417
+ | `keySize` | `32` |
418
+ | `blockSize` | `16` |
419
+ | `counterSize` | `16` |
420
+ | `wasmModules` | `['serpent']` |
421
+
422
+ ### `SerpentGenerator.generate(key, counter, n): Uint8Array`
423
+
424
+ Produces `n` bytes of pseudorandom output from `(key, counter)`. Neither input
425
+ is mutated. Wipes WASM key/key-schedule/scratch and the JS-heap counter copy
426
+ before returning.
427
+
428
+ | Parameter | Type | Description |
429
+ |-----------|------|-------------|
430
+ | `key` | `Uint8Array` | 32 bytes (256-bit Serpent key) |
431
+ | `counter` | `Uint8Array` | 16 bytes, treated as a little-endian integer |
432
+ | `n` | `number` | Output byte count: 0 ≤ n ≤ 2³⁰ |
433
+
434
+ **Returns** a new `Uint8Array` of length `n`.
435
+
436
+ **Throws:**
437
+ - `RangeError('SerpentGenerator: key must be 32 bytes (got N)')` if key length ≠ 32
438
+ - `RangeError('SerpentGenerator: counter must be 16 bytes (got N)')` if counter length ≠ 16
439
+ - `RangeError('SerpentGenerator: n must be a non-negative safe integer <= 2^30 (got N)')` if n is out of range
440
+ - `Error` if another stateful instance currently owns the `serpent` WASM module
441
+
442
+ ### Usage with `Fortuna`
443
+
444
+ ```typescript
445
+ import { init, Fortuna } from 'leviathan-crypto'
446
+ import { SerpentGenerator } from 'leviathan-crypto/serpent'
447
+ import { SHA256Hash } from 'leviathan-crypto/sha2'
448
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
449
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
450
+
451
+ await init({ serpent: serpentWasm, sha2: sha2Wasm })
452
+ const rng = await Fortuna.create({ generator: SerpentGenerator, hash: SHA256Hash })
453
+ const bytes = rng.get(32)
454
+ rng.stop()
455
+ ```
456
+
362
457
  ---
363
458
 
364
459
  ## Usage Examples
@@ -383,9 +478,8 @@ console.log(new TextDecoder().decode(decrypted))
383
478
 
384
479
  ### Example 2: CTR mode (advanced)
385
480
 
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.
481
+ Use `SerpentCtr` to encrypt data of any length. CTR mode produces ciphertext
482
+ the same length as the plaintext with no padding overhead.
389
483
 
390
484
  ```typescript
391
485
  import { init, SerpentCtr, randomBytes } from 'leviathan-crypto';
@@ -417,12 +511,11 @@ ctr.dispose();
417
511
 
418
512
  > [!IMPORTANT]
419
513
  > CTR mode is unauthenticated. An attacker can tamper with the
420
- > ciphertext without detection. Use `Seal` with `SerpentCipher` for authenticated encryption.
514
+ > ciphertext without detection. Use [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher) for authenticated encryption.
421
515
 
422
516
  ### Example 3: CBC mode (advanced)
423
517
 
424
- For authenticated encryption, use `Seal` with `SerpentCipher`. Use `SerpentCbc` for
425
- message-level encryption with automatic PKCS7 padding.
518
+ Use `SerpentCbc` for message-level encryption with automatic PKCS7 padding.
426
519
 
427
520
  ```typescript
428
521
  import { init, SerpentCbc, randomBytes } from 'leviathan-crypto';
@@ -448,12 +541,11 @@ cbc.dispose();
448
541
  ```
449
542
 
450
543
  > [!IMPORTANT]
451
- > CBC mode is unauthenticated. Use `Seal` with `SerpentCipher` for authenticated encryption.
544
+ > CBC mode is unauthenticated. Use [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher) for authenticated encryption.
452
545
 
453
546
  ### Example 4: Raw block operations (low-level)
454
547
 
455
548
  Use the `Serpent` class for single 16-byte block operations. This is the lowest-level
456
- API; use `Seal` with `SerpentCipher` for most purposes.
457
549
 
458
550
  ```typescript
459
551
  import { init, Serpent } from 'leviathan-crypto';
@@ -499,21 +591,27 @@ cipher.dispose();
499
591
  | Nonce is not 16 bytes (`SerpentCtr`) | `RangeError` | `nonce must be 16 bytes (got N)` |
500
592
  | Chunk exceeds buffer size (`SerpentCtr`) | `RangeError` | `chunk exceeds maximum size of N bytes — split into smaller chunks` |
501
593
  | IV is not 16 bytes (`SerpentCbc`) | `RangeError` | `CBC IV must be 16 bytes (got N)` |
502
- | Ciphertext length is zero or not a multiple of 16 (`SerpentCbc.decrypt`) | `RangeError` | `ciphertext length must be a non-zero multiple of 16` |
503
- | Invalid PKCS7 padding on decrypt (`SerpentCbc.decrypt`) | `RangeError` | `invalid PKCS7 padding` |
594
+ | Ciphertext length zero, not a multiple of 16, or PKCS7 padding invalid (`SerpentCbc.decrypt`) | `RangeError` | `invalid ciphertext` (same message for every failure mode no numeric leak) |
595
+ | `SerpentGenerator.generate()` key 32 bytes | `RangeError` | `SerpentGenerator: key must be 32 bytes (got N)` |
596
+ | `SerpentGenerator.generate()` counter ≠ 16 bytes | `RangeError` | `SerpentGenerator: counter must be 16 bytes (got N)` |
597
+ | `SerpentGenerator.generate()` n out of range | `RangeError` | `SerpentGenerator: n must be a non-negative safe integer <= 2^30 (got N)` |
504
598
 
505
599
  ---
506
600
 
507
- > ## Cross-References
508
- >
509
- > - [index](./README.md) — Project Documentation index
510
- > - [lexicon](./lexicon.md) Glossary of cryptographic terms
511
- > - [architecture](./architecture.md) architecture overview, module relationships, buffer layouts, and build pipeline
512
- > - [asm_serpent](./asm_serpent.md) WASM implementation details and buffer layout
513
- > - [serpent_reference](./serpent_reference.md) algorithm specification, S-boxes, linear transform, and known attacks
514
- > - [serpent_audit](./serpent_audit.md) security audit findings (correctness, side-channel analysis)
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`
518
- > - [types](./types.md) `Blockcipher`, `Streamcipher`, and `AEAD` interfaces implemented by Serpent classes
519
- > - [utils](./utils.md) `constantTimeEqual`, `wipe`, `randomBytes` used by Serpent wrappers
601
+
602
+ ## Cross-References
603
+
604
+ | Document | Description |
605
+ | -------- | ----------- |
606
+ | [index](./README.md) | Project Documentation index |
607
+ | [lexicon](./lexicon.md) | Glossary of cryptographic terms |
608
+ | [architecture](./architecture.md) | architecture overview, module relationships, buffer layouts, and build pipeline |
609
+ | [asm_serpent](./asm_serpent.md) | WASM implementation details and buffer layout |
610
+ | [serpent_reference](./serpent_reference.md) | algorithm specification, S-boxes, linear transform, and known attacks |
611
+ | [serpent_audit](./serpent_audit.md) | security audit findings (correctness, side-channel analysis) |
612
+ | [authenticated encryption](./aead.md) | `Seal`, `SealStream`, `OpenStream`: use `SerpentCipher` as the suite argument |
613
+ | [chacha20](./chacha20.md) | `XChaCha20Cipher`: alternative `CipherSuite` for `Seal` and streaming |
614
+ | [sha2](./sha2.md) | HMAC-SHA256 and HKDF used internally by `SerpentCipher` |
615
+ | [types](./types.md) | `Blockcipher`, `Streamcipher`, and `AEAD` interfaces implemented by Serpent classes |
616
+ | [utils](./utils.md) | `constantTimeEqual`, `wipe`, `randomBytes` used by Serpent wrappers |
617
+
package/dist/docs/sha2.md CHANGED
@@ -1,16 +1,15 @@
1
- # SHA-2 TypeScript API
1
+ <img src="https://github.com/xero/leviathan-crypto/raw/main/docs/logo.svg" alt="logo" width="120" align="left" margin="10">
2
2
 
3
- > [!NOTE]
4
- > Cryptographic hashing and message authentication using SHA-256, SHA-384,
5
- > SHA-512, HMAC-SHA256, HMAC-SHA384, and HMAC-SHA512.
6
- >
7
- > See [SHA-2 implementation audit](./sha2_audit.md), [HMAC audit](./hmac_audit.md), and [HKDF audit](./hkdf_audit.md) for algorithm correctness verifications.
3
+ ### SHA-2 TypeScript API
4
+
5
+ Cryptographic hashing and message authentication using SHA-256, SHA-384, SHA-512, HMAC-SHA256, HMAC-SHA384, and HMAC-SHA512.
8
6
 
9
7
  > ### Table of Contents
10
8
  > - [Overview](#overview)
11
9
  > - [Security Notes](#security-notes)
12
10
  > - [Module Init](#module-init)
13
11
  > - [API Reference](#api-reference)
12
+ > - [SHA256Hash](#sha256hash)
14
13
  > - [Usage Examples](#usage-examples)
15
14
  > - [Error Conditions](#error-conditions)
16
15
 
@@ -113,6 +112,9 @@ time (this is called a **timing attack**).
113
112
  Use `constantTimeEqual()` from leviathan-crypto instead. It always compares
114
113
  every byte regardless of where the first difference is.
115
114
 
115
+ Note: `constantTimeEqual` requires WebAssembly SIMD; on non-SIMD runtimes it
116
+ throws at first call. See [utils.md](./utils.md#constanttimeequal).
117
+
116
118
  ### Call dispose() when you are done
117
119
 
118
120
  `dispose()` calls `wipeBuffers()` in the WASM module, which zeroes out all
@@ -120,6 +122,9 @@ internal buffers including hash state and key material. This prevents sensitive
120
122
  data from lingering in memory. Always call `dispose()` when you are finished
121
123
  with a hash or HMAC instance.
122
124
 
125
+ > [!NOTE]
126
+ > See [SHA-2 implementation audit](./sha2_audit.md), [HMAC audit](./hmac_audit.md), and [HKDF audit](./hkdf_audit.md) for algorithm correctness verifications.
127
+
123
128
  ---
124
129
 
125
130
  ## Module Init
@@ -323,10 +328,16 @@ class HKDF_SHA512 {
323
328
  }
324
329
  ```
325
330
 
331
+ **`constructor()`** Creates a new `HKDF_SHA512` instance. Throws if `init({ sha2: sha2Wasm })` has not been called.
332
+
326
333
  **`extract(salt, ikm)`** If `salt` is `null` or empty, defaults to 64 zero bytes.
327
334
 
328
335
  **`expand(prk, info, length)`** PRK must be exactly 64 bytes. `length` must be between 1 and 16320. Throws `RangeError` otherwise.
329
336
 
337
+ **`derive(ikm, salt, info, length): Uint8Array`** One-shot: calls `extract(salt, ikm)` then `expand(prk, info, length)`. This is the correct path for most callers.
338
+
339
+ **`dispose(): void`** Releases the internal HMAC instance.
340
+
330
341
  > [!NOTE]
331
342
  > HKDF is a pure TypeScript composition over the WASM-backed HMAC classes.
332
343
  > It does not introduce new WASM code or new `init()` modules. Initializing
@@ -353,6 +364,49 @@ hkdf.dispose()
353
364
 
354
365
  ---
355
366
 
367
+ ## SHA256Hash
368
+
369
+ Stateless SHA-256 `HashFn` for Fortuna's accumulator and reseed slots. Plain
370
+ `const` object — no instantiation, no `dispose()`.
371
+
372
+ Requires `init({ sha2: sha2Wasm })`. See [fortuna.md](./fortuna.md) for full
373
+ usage with `Fortuna.create()`.
374
+
375
+ | Property | Value |
376
+ |----------|-------|
377
+ | `outputSize` | `32` |
378
+ | `wasmModules` | `['sha2']` |
379
+
380
+ ### `SHA256Hash.digest(msg): Uint8Array`
381
+
382
+ Hashes `msg` and returns a 32-byte SHA-256 digest. Wipes WASM input/output/state
383
+ scratch before returning.
384
+
385
+ | Parameter | Type | Description |
386
+ |-----------|------|-------------|
387
+ | `msg` | `Uint8Array` | Message to hash (any length) |
388
+
389
+ **Returns** a new `Uint8Array` of 32 bytes.
390
+
391
+ **Throws** `Error` if another stateful instance currently owns the `sha2` WASM module.
392
+
393
+ ### Usage with `Fortuna`
394
+
395
+ ```typescript
396
+ import { init, Fortuna } from 'leviathan-crypto'
397
+ import { SHA256Hash } from 'leviathan-crypto/sha2'
398
+ import { SerpentGenerator } from 'leviathan-crypto/serpent'
399
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
400
+ import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
401
+
402
+ await init({ sha2: sha2Wasm, serpent: serpentWasm })
403
+ const rng = await Fortuna.create({ generator: SerpentGenerator, hash: SHA256Hash })
404
+ const bytes = rng.get(32)
405
+ rng.stop()
406
+ ```
407
+
408
+ ---
409
+
356
410
  ## Usage Examples
357
411
 
358
412
  ### Example 1: Hash a message with SHA-256
@@ -598,16 +652,20 @@ SHA-2 is well-defined for zero-length messages and will return the correct diges
598
652
 
599
653
  ---
600
654
 
601
- > ## Cross-References
602
- >
603
- > - [index](./README.md) — Project Documentation index
604
- > - [architecture](./architecture.md) architecture overview, module relationships, buffer layouts, and build pipeline
605
- > - [asm_sha2](./asm_sha2.md) WASM implementation details (AssemblyScript buffer layout, compression functions)
606
- > - [sha3](./sha3.md) alternative: SHA-3 family (immune to length extension attacks)
607
- > - [serpent](./serpent.md) `SerpentCipher` uses HMAC-SHA256 and HKDF internally via `Seal` and `SealStream`
608
- > - [argon2id](./argon2id.md) Argon2id password hashing; HKDF expands Argon2id root keys
609
- > - [fortuna](./fortuna.md) Fortuna CSPRNG uses SHA-256 for entropy accumulation
610
- > - [utils](./utils.md) `constantTimeEqual`, `bytesToHex`, `utf8ToBytes`, `randomBytes`
611
- > - [sha2_audit.md](./sha2_audit.md) SHA-2 implementation audit
612
- > - [hmac_audit.md](./hmac_audit.md) HMAC implementation audit
613
- > - [hkdf_audit.md](./hkdf_audit.md) HKDF implementation audit
655
+
656
+ ## Cross-References
657
+
658
+ | Document | Description |
659
+ | -------- | ----------- |
660
+ | [index](./README.md) | Project Documentation index |
661
+ | [architecture](./architecture.md) | architecture overview, module relationships, buffer layouts, and build pipeline |
662
+ | [asm_sha2](./asm_sha2.md) | WASM implementation details (AssemblyScript buffer layout, compression functions) |
663
+ | [sha3](./sha3.md) | alternative: SHA-3 family (immune to length extension attacks) |
664
+ | [serpent](./serpent.md) | `SerpentCipher` uses HMAC-SHA256 and HKDF internally via `Seal` and `SealStream` |
665
+ | [argon2id](./argon2id.md) | Argon2id password hashing; HKDF expands Argon2id root keys |
666
+ | [fortuna](./fortuna.md) | Fortuna CSPRNG uses SHA-256 for entropy accumulation |
667
+ | [utils](./utils.md) | `constantTimeEqual`, `bytesToHex`, `utf8ToBytes`, `randomBytes` |
668
+ | [sha2_audit.md](./sha2_audit.md) | SHA-2 implementation audit |
669
+ | [hmac_audit.md](./hmac_audit.md) | HMAC implementation audit |
670
+ | [hkdf_audit.md](./hkdf_audit.md) | HKDF implementation audit |
671
+
package/dist/docs/sha3.md CHANGED
@@ -1,7 +1,8 @@
1
- # SHA3 TypeScript API Reference
1
+ <img src="https://github.com/xero/leviathan-crypto/raw/main/docs/logo.svg" alt="logo" width="120" align="left" margin="10">
2
2
 
3
- > [!NOTE]
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.
3
+ ### SHA3 TypeScript API Reference
4
+
5
+ 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
 
6
7
  > ### Table of Contents
7
8
  > - [Overview](#overview)
@@ -9,6 +10,7 @@
9
10
  > - [Module Init](#module-init)
10
11
  > - [API Reference](#api-reference)
11
12
  > - [Incremental XOF API](#incremental-xof-api-absorb--squeeze--reset)
13
+ > - [SHA3_256Hash](#sha3_256hash)
12
14
  > - [Usage Examples](#usage-examples)
13
15
  > - [Error Conditions](#error-conditions)
14
16
 
@@ -207,6 +209,12 @@ class SHA3_512 {
207
209
  Extendable-output function (XOF). Produces **variable-length** output — any
208
210
  number of bytes you request. 128-bit security level.
209
211
 
212
+ > [!CAUTION]
213
+ > `SHAKE128` is stateful and holds exclusive access to the `sha3` WASM module
214
+ > for its entire lifetime. Constructing a second SHAKE128/SHAKE256 or any
215
+ > other sha3 class (`SHA3_256`, etc.) while this instance is live throws.
216
+ > Call `dispose()` when done. Pool workers are unaffected.
217
+
210
218
  ```typescript
211
219
  class SHAKE128 {
212
220
  constructor()
@@ -223,11 +231,15 @@ class SHAKE128 {
223
231
  | `hash(msg, outputLength)` | One-shot: reset, absorb, squeeze. Safe on a dirty instance. |
224
232
  | `absorb(msg)` | Feed data into the sponge. Chainable. Throws if called after `squeeze()`. |
225
233
  | `squeeze(n)` | Pull `n` bytes of XOF output. Output is contiguous — `squeeze(a)` followed by `squeeze(b)` yields bytes `[0, a)` and `[a, a+b)` of the XOF stream. |
226
- | `reset()` | Return to a fresh, zeroed state. Chainable. Safe at any point. |
227
- | `dispose()` | Zero all WASM state and the TS-side block buffer. |
234
+ | `reset()` | Return to a fresh, zeroed state. Chainable. Safe at any point. Does not release the sha3 exclusivity token. |
235
+ | `dispose()` | Zero all WASM state and the TS-side block buffer, release the sha3 exclusivity token. Idempotent. |
228
236
 
229
237
  **`outputLength`** / **`n`** must be `>= 1`. Values below 1 throw a `RangeError`.
230
238
 
239
+ After `dispose()`, all instance methods (`reset`, `absorb`, `squeeze`, `hash`)
240
+ throw `Error: SHAKE128: instance has been disposed`. Disposal is permanent;
241
+ construct a new instance if you need to continue.
242
+
231
243
  ---
232
244
 
233
245
  ### SHAKE256
@@ -235,6 +247,12 @@ class SHAKE128 {
235
247
  Extendable-output function (XOF). Produces **variable-length** output — any
236
248
  number of bytes you request. 256-bit security level.
237
249
 
250
+ > [!CAUTION]
251
+ > `SHAKE256` is stateful and holds exclusive access to the `sha3` WASM module
252
+ > for its entire lifetime. Constructing a second SHAKE128/SHAKE256 or any
253
+ > other sha3 class while this instance is live throws. Call `dispose()` when
254
+ > done.
255
+
238
256
  ```typescript
239
257
  class SHAKE256 {
240
258
  constructor()
@@ -251,11 +269,15 @@ class SHAKE256 {
251
269
  | `hash(msg, outputLength)` | One-shot: reset, absorb, squeeze. Safe on a dirty instance. |
252
270
  | `absorb(msg)` | Feed data into the sponge. Chainable. Throws if called after `squeeze()`. |
253
271
  | `squeeze(n)` | Pull `n` bytes of XOF output. Output is contiguous — `squeeze(a)` followed by `squeeze(b)` yields bytes `[0, a)` and `[a, a+b)` of the XOF stream. |
254
- | `reset()` | Return to a fresh, zeroed state. Chainable. Safe at any point. |
255
- | `dispose()` | Zero all WASM state and the TS-side block buffer. |
272
+ | `reset()` | Return to a fresh, zeroed state. Chainable. Safe at any point. Does not release the sha3 exclusivity token. |
273
+ | `dispose()` | Zero all WASM state and the TS-side block buffer, release the sha3 exclusivity token. Idempotent. |
256
274
 
257
275
  **`outputLength`** / **`n`** must be `>= 1`. Values below 1 throw a `RangeError`.
258
276
 
277
+ After `dispose()`, all instance methods (`reset`, `absorb`, `squeeze`, `hash`)
278
+ throw `Error: SHAKE256: instance has been disposed`. Disposal is permanent;
279
+ construct a new instance if you need to continue.
280
+
259
281
  ---
260
282
 
261
283
  ## Incremental XOF API (`absorb` / `squeeze` / `reset`)
@@ -298,6 +320,49 @@ xof.dispose()
298
320
 
299
321
  ---
300
322
 
323
+ ## SHA3_256Hash
324
+
325
+ Stateless SHA3-256 `HashFn` for Fortuna's accumulator and reseed slots. Plain
326
+ `const` object — no instantiation, no `dispose()`.
327
+
328
+ Requires `init({ sha3: sha3Wasm })` (or the `keccak` alias). See
329
+ [fortuna.md](./fortuna.md) for full usage with `Fortuna.create()`.
330
+
331
+ | Property | Value |
332
+ |----------|-------|
333
+ | `outputSize` | `32` |
334
+ | `wasmModules` | `['sha3']` |
335
+
336
+ ### `SHA3_256Hash.digest(msg): Uint8Array`
337
+
338
+ Hashes `msg` and returns a 32-byte SHA3-256 digest. Wipes WASM input/output/sponge
339
+ state scratch before returning.
340
+
341
+ | Parameter | Type | Description |
342
+ |-----------|------|-------------|
343
+ | `msg` | `Uint8Array` | Message to hash (any length) |
344
+
345
+ **Returns** a new `Uint8Array` of 32 bytes.
346
+
347
+ **Throws** `Error` if another stateful instance currently owns the `sha3` WASM module.
348
+
349
+ ### Usage with `Fortuna`
350
+
351
+ ```typescript
352
+ import { init, Fortuna } from 'leviathan-crypto'
353
+ import { SHA3_256Hash } from 'leviathan-crypto/sha3'
354
+ import { ChaCha20Generator } from 'leviathan-crypto/chacha20'
355
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
356
+ import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
357
+
358
+ await init({ sha3: sha3Wasm, chacha20: chacha20Wasm })
359
+ const rng = await Fortuna.create({ generator: ChaCha20Generator, hash: SHA3_256Hash })
360
+ const bytes = rng.get(32)
361
+ rng.stop()
362
+ ```
363
+
364
+ ---
365
+
301
366
  ## Usage Examples
302
367
 
303
368
  ### Example 1: Hash a string with SHA3-256
@@ -536,11 +601,12 @@ absorbs zero bytes and then squeezes.
536
601
 
537
602
  ---
538
603
 
539
- > ## Cross-References
540
- >
541
- > - [index](./README.md) — Project Documentation index
542
- > - [asm_sha3](./asm_sha3.md): WASM implementation details (buffer layout, Keccak internals, variant parameters)
543
- > - [sha2](./sha2.md): Alternative: SHA-2 family (SHA-256, SHA-384, SHA-512) and HMAC
544
- > - [utils](./utils.md): Encoding utilities: `bytesToHex`, `hexToBytes`, `utf8ToBytes`
545
- > - [architecture](./architecture.md) architecture overview, module relationships, buffer layouts, and build pipeline
546
- > - [sha3_audit.md](./sha3_audit.md) SHA-3 / Keccak implementation audit
604
+
605
+ ## Cross-References
606
+
607
+ | Document | Description |
608
+ | -------- | ----------- |
609
+ | [index](./README.md) | Project Documentation index |
610
+ | [architecture](./architecture.md) | architecture overview, module relationships, buffer layouts, and build pipeline |
611
+ | [sha3_audit.md](./sha3_audit.md) | SHA-3 / Keccak implementation audit |
612
+