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,11 +1,14 @@
1
- # TypeScript Interfaces
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
- > 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.
3
+ ### TypeScript Interfaces
4
+
5
+ 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.
5
6
 
6
7
  > ### Table of Contents
7
8
  > - [API Reference](#api-reference)
8
9
  > - [Usage Examples](#usage-examples)
10
+ > - [Generator](#generator)
11
+ > - [HashFn](#hashfn)
9
12
  > - [WasmSource](#wasmsource)
10
13
  > - [CipherSuite](#ciphersuite)
11
14
  > - [DerivedKeys](#derivedkeys)
@@ -188,6 +191,60 @@ function cleanup(ctx: EncryptionContext): void {
188
191
 
189
192
  ---
190
193
 
194
+ ## Generator
195
+
196
+ ```typescript
197
+ interface Generator {
198
+ readonly keySize: number; // bytes
199
+ readonly blockSize: number; // bytes per cipher block
200
+ readonly counterSize: number; // bytes
201
+ readonly wasmModules: readonly string[];
202
+ generate(key: Uint8Array, counter: Uint8Array, n: number): Uint8Array;
203
+ }
204
+ ```
205
+
206
+ Stateless cipher PRF. Used by `Fortuna` as the generator slot.
207
+ Implementations are plain const objects; they assert that no stateful
208
+ instance owns the underlying WASM module before each call but do not
209
+ acquire it themselves.
210
+
211
+ | Member | Description |
212
+ |---|---|
213
+ | `keySize` | Generator key size in bytes. Must equal the paired `HashFn.outputSize` when used with `Fortuna`. |
214
+ | `blockSize` | Bytes per cipher block. Used by `Fortuna` to compute counter advancement. |
215
+ | `counterSize` | Counter width in bytes. `Fortuna` allocates its `genCnt` of this size. |
216
+ | `wasmModules` | List of WASM module names the generator depends on. Used for `init()` preflight in `Fortuna.create()`. |
217
+ | `generate(key, counter, n)` | Produces `n` bytes of keystream from `(key, counter)`. Stateless; does not mutate either input. |
218
+
219
+ Shipped implementations: `SerpentGenerator` (from `'leviathan-crypto/serpent'`), `ChaCha20Generator` (from `'leviathan-crypto/chacha20'`).
220
+
221
+ ---
222
+
223
+ ## HashFn
224
+
225
+ ```typescript
226
+ interface HashFn {
227
+ readonly outputSize: number; // bytes
228
+ readonly wasmModules: readonly string[];
229
+ digest(msg: Uint8Array): Uint8Array;
230
+ }
231
+ ```
232
+
233
+ Stateless hash function. Used by `Fortuna` for the accumulator chain and
234
+ the reseed key derivation. Distinct from the existing `Hash` interface
235
+ above, which describes class-shaped instances that own scratch state and
236
+ require `dispose()`.
237
+
238
+ | Member | Description |
239
+ |---|---|
240
+ | `outputSize` | Digest size in bytes. Must equal the paired `Generator.keySize` when used with `Fortuna`. |
241
+ | `wasmModules` | List of WASM module names the hash depends on. |
242
+ | `digest(msg)` | Produces a digest of the input. Stateless; safe to call concurrently with itself within a single JavaScript turn. |
243
+
244
+ Shipped implementations: `SHA256Hash` (from `'leviathan-crypto/sha2'`), `SHA3_256Hash` (from `'leviathan-crypto/sha3'`).
245
+
246
+ ---
247
+
191
248
  ## WasmSource
192
249
 
193
250
  Union type for WASM module sources. Accepted by `init()`, `serpentInit()`, etc.
@@ -210,15 +267,16 @@ Cipher-specific logic injected into `SealStream` and `OpenStream`.
210
267
  | `kemCtSize` | `number` | KEM ciphertext byte length appended to the header in the preamble. `0` for symmetric suites |
211
268
  | `tagSize` | `number` | Authentication tag size in bytes |
212
269
  | `padded` | `boolean` | Whether ciphertext includes padding (PKCS7 for CBC) |
270
+ | `wasmChunkSize` | `number` | WASM buffer capacity for one padded chunk. `SealStreamPool.create()` validates `paddedFull ≤ wasmChunkSize` at startup for padded ciphers and throws `RangeError` if the check fails. `SerpentCipher`: 65552. `XChaCha20Cipher`: 65536. `KyberSuite` forwards from its inner cipher. |
213
271
  | `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
272
 
215
273
  | Method | Signature | Description |
216
274
  |--------|-----------|-------------|
217
- | `deriveKeys` | `(masterKey, nonce) → DerivedKeys` | HKDF key derivation |
275
+ | `deriveKeys` | `(masterKey, nonce, kemCt?) → DerivedKeys` | HKDF key derivation. `kemCt` is the KEM ciphertext; present only for hybrid suites. |
218
276
  | `sealChunk` | `(keys, counterNonce, chunk, aad?) → Uint8Array` | Encrypt one chunk |
219
277
  | `openChunk` | `(keys, counterNonce, chunk, aad?) → Uint8Array` | Decrypt one chunk |
220
278
  | `wipeKeys` | `(keys) → void` | Zero derived key material |
221
- | `createPoolWorker` | `() → Worker` | Create a Web Worker for pool use |
279
+ | `createPoolWorker` | `() → Worker` | Create a Web Worker for pool use. Default spawns a classic worker from a blob URL over a build-time IIFE; override via spread for strict-CSP environments. See [ciphersuite.md](./ciphersuite.md). |
222
280
 
223
281
  Implementations: `XChaCha20Cipher`, `SerpentCipher` (plain `const` objects, not classes), and `KyberSuite` (factory function returning a `CipherSuite`). See [ciphersuite.md](./ciphersuite.md).
224
282
 
@@ -235,6 +293,7 @@ Opaque key material returned by `CipherSuite.deriveKeys()`.
235
293
  | Field | Type | Description |
236
294
  |-------|------|-------------|
237
295
  | `bytes` | `readonly Uint8Array` | Raw derived key bytes (opaque to the stream layer) |
296
+ | `kemCiphertext?` | `readonly Uint8Array \| undefined` | KEM ciphertext produced during encapsulation. Present only for hybrid KEM suites; absent for symmetric suites. |
238
297
 
239
298
  ---
240
299
 
@@ -263,13 +322,95 @@ Options for `SealStreamPool.create()`.
263
322
 
264
323
  ---
265
324
 
266
- > ## Cross-References
267
- >
268
- > - [index](./README.md) Project Documentation index
269
- > - [architecture](./architecture.md) — architecture overview, module relationships, buffer layouts, and build pipeline
270
- > - [utils](./utils.md) — encoding utilities and `constantTimeEqual` for verifying MACs from `KeyedHash`
271
- > - [serpent](./serpent.md) — Serpent classes implement `Blockcipher`, `Streamcipher`, and `AEAD`
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
273
- > - [sha2](./sha2.md) — SHA-2 classes implement `Hash`; HMAC classes implement `KeyedHash`
274
- > - [sha3](./sha3.md) — SHA-3 classes implement `Hash`; SHAKE classes extend with XOF API
275
- > - [test-suite](./test-suite.md) — test suite structure and vector corpus
325
+ ## Ratchet types
326
+
327
+ Shared types for the ratchet KDF module. See [ratchet.md](./ratchet.md) for full API.
328
+
329
+ ### MlKemLike
330
+
331
+ Structural interface satisfied by `MlKem512`, `MlKem768`, and `MlKem1024`. Used as the `kem` parameter type for `kemRatchetEncap`, `kemRatchetDecap`, and `RatchetKeypair`.
332
+
333
+ ```typescript
334
+ interface MlKemLike {
335
+ readonly params: KyberParams
336
+ keygen(): { encapsulationKey: Uint8Array; decapsulationKey: Uint8Array }
337
+ encapsulate(ek: Uint8Array): { ciphertext: Uint8Array; sharedSecret: Uint8Array }
338
+ decapsulate(dk: Uint8Array, ct: Uint8Array): Uint8Array
339
+ }
340
+ ```
341
+
342
+ ### RatchetInitResult
343
+
344
+ ```typescript
345
+ interface RatchetInitResult {
346
+ readonly nextRootKey: Uint8Array // 32 bytes
347
+ readonly sendChainKey: Uint8Array // 32 bytes
348
+ readonly recvChainKey: Uint8Array // 32 bytes
349
+ }
350
+ ```
351
+
352
+ ### KemEncapResult
353
+
354
+ ```typescript
355
+ interface KemEncapResult {
356
+ readonly nextRootKey: Uint8Array // 32 bytes
357
+ readonly sendChainKey: Uint8Array // 32 bytes
358
+ readonly recvChainKey: Uint8Array // 32 bytes
359
+ readonly kemCt: Uint8Array // ML-KEM ciphertext — transmit in-band
360
+ }
361
+ ```
362
+
363
+ ### KemDecapResult
364
+
365
+ ```typescript
366
+ interface KemDecapResult {
367
+ readonly nextRootKey: Uint8Array // 32 bytes
368
+ readonly sendChainKey: Uint8Array // 32 bytes
369
+ readonly recvChainKey: Uint8Array // 32 bytes
370
+ }
371
+ ```
372
+
373
+ ### RatchetMessageHeader
374
+
375
+ ```typescript
376
+ interface RatchetMessageHeader {
377
+ readonly epoch: number // sender's epoch at seal time; starts 0, increments on ratchet step
378
+ readonly counter: number // KDFChain.n at seal time (post-step value, first message = 1)
379
+ readonly pn?: number // previous chain length — present only on the first message of a new epoch
380
+ readonly kemCt?: Uint8Array // ML-KEM ciphertext — present only on the first message of a new epoch (encap side)
381
+ }
382
+ ```
383
+
384
+ Canonical header shape for a ratchet-protected message. `pn` and `kemCt` are absent on every message except the first one of a new epoch, where both must be present together.
385
+
386
+ ### ResolveHandle
387
+
388
+ Return type of `SkippedKeyStore.resolve()`.
389
+
390
+ ```typescript
391
+ interface ResolveHandle {
392
+ readonly key: Uint8Array // 32-byte message key — throws after settlement
393
+ commit(): void // wipe key and mark settled — call on successful decrypt
394
+ rollback(): void // return key to store and mark settled — call on auth failure
395
+ }
396
+ ```
397
+
398
+ `commit()` and `rollback()` are mutually exclusive; calling either a second time (or calling the other after settling) throws `Error: 'SkippedKeyStore: handle already settled'`. Accessing `.key` after settlement also throws. This enforces the delete-on-use contract: a key is consumed exactly once, either by committing (decrypt succeeded, key wiped) or rolling back (decrypt failed, key returned to the store for a future legitimate delivery at the same counter).
399
+
400
+ ---
401
+
402
+
403
+ ## Cross-References
404
+
405
+ | Document | Description |
406
+ | -------- | ----------- |
407
+ | [index](./README.md) | Project Documentation index |
408
+ | [architecture](./architecture.md) | architecture overview, module relationships, buffer layouts, and build pipeline |
409
+ | [utils](./utils.md) | encoding utilities and `constantTimeEqual` for verifying MACs from `KeyedHash` |
410
+ | [serpent](./serpent.md) | Serpent classes implement `Blockcipher`, `Streamcipher`, and `AEAD` |
411
+ | [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 |
412
+ | [sha2](./sha2.md) | SHA-2 classes implement `Hash`; HMAC classes implement `KeyedHash` |
413
+ | [sha3](./sha3.md) | SHA-3 classes implement `Hash`; SHAKE classes extend with XOF API |
414
+ | [ratchet](./ratchet.md) | ratchet KDF primitives; `MlKemLike`, `RatchetInitResult`, `KemEncapResult`, `KemDecapResult`, `RatchetMessageHeader`, `ResolveHandle` |
415
+ | [test-suite](./test-suite.md) | test suite structure and vector corpus |
416
+
@@ -1,7 +1,8 @@
1
- # Utilities
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
- > Pure TypeScript utilities that ship alongside the WASM-backed primitives. No `init()` call required; all functions work immediately on import.
3
+ ### Utilities
4
+
5
+ Pure TypeScript utilities that ship alongside the WASM-backed primitives. No `init()` call required; all functions work immediately on import.
5
6
 
6
7
  > ### Table of Contents
7
8
  > - [Security Notes](#security-notes)
@@ -11,125 +12,201 @@
11
12
 
12
13
  ---
13
14
 
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
+ The module covers encoding (hex, UTF-8, and base64 conversions between strings
16
+ and `Uint8Array`), security (constant-time comparison and secure memory
17
+ wiping), byte manipulation (XOR and concatenation), and random byte generation.
15
18
 
16
19
  ## Security Notes
17
20
 
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.
21
+ **`constantTimeEqual`** runs inside a WASM SIMD module that removes the JS JIT
22
+ compiler from the timing picture. Use this function whenever you compare MACs,
23
+ hashes, authentication tags, or any secret-derived values. Never use `===`,
24
+ `Buffer.equals`, or a manual loop-with-break for security comparisons. Those
25
+ leak timing information that attackers can exploit to recover secrets. Inputs
26
+ are limited to [`CT_MAX_BYTES`](#ct_max_bytes) (32768 bytes) per side.
19
27
 
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.
28
+ The length check in `constantTimeEqual` is not constant-time, because array
29
+ length is non-secret in all standard protocols. If your use case treats length
30
+ as secret, pad to equal length before comparing.
21
31
 
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.
32
+ **`wipe`** zeroes a typed array in-place. Call it on keys, plaintext buffers,
33
+ and any other sensitive data as soon as you are done with them. JavaScript's
34
+ garbage collector does not guarantee timely or complete erasure of memory.
23
35
 
24
- **`randomBytes`** delegates to `crypto.getRandomValues` (Web Crypto API), which is cryptographically secure in all modern browsers and Node.js 19+. It does not fall back to `Math.random` or any insecure source.
36
+ **`randomBytes`** delegates to `crypto.getRandomValues` (Web Crypto API), which
37
+ is cryptographically secure in all modern browsers and Node.js 19+. It does not
38
+ fall back to `Math.random` or any insecure source.
25
39
 
26
- The encoding functions (`hexToBytes`, `bytesToHex`, `utf8ToBytes`, `bytesToUtf8`, `base64ToBytes`, `bytesToBase64`) perform no security-sensitive operations.
40
+ The encoding functions (`hexToBytes`, `bytesToHex`, `utf8ToBytes`,
41
+ `bytesToUtf8`, `base64ToBytes`, `bytesToBase64`) perform no security-sensitive
42
+ operations.
27
43
 
28
44
  ---
29
45
 
30
46
  ## API Reference
31
47
 
32
- ### hexToBytes
48
+ ### constantTimeEqual
33
49
 
34
50
  ```typescript
35
- hexToBytes(hex: string): Uint8Array
51
+ constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean
36
52
  ```
37
53
 
38
- Converts a hex string to a `Uint8Array`. Accepts lowercase or uppercase characters. An optional `0x` or `0X` prefix is stripped automatically. Throws `RangeError` on odd-length input.
54
+ Returns `true` if `a` and `b` contain identical bytes. Returns `false`
55
+ immediately if the arrays differ in length (length is non-secret in all
56
+ standard protocols).
57
+
58
+ The comparison runs inside a WASM SIMD module, removing the JS JIT compiler
59
+ from the timing picture. Speculative optimisation and branch prediction inside
60
+ the engine cannot short-circuit the loop. This function requires WebAssembly
61
+ SIMD; on runtimes without SIMD support it throws `Error: leviathan-crypto:
62
+ constantTimeEqual requires WebAssembly SIMD — this runtime does not support it`
63
+ at first call. SIMD has been a baseline feature of all major browsers and
64
+ Node.js 16.4+ since 2021, and the library's symmetric and post-quantum
65
+ primitives require SIMD already; this function tightens that posture
66
+ consistently.
67
+
68
+ The WASM module ORs the two 64-bit lanes of the SIMD accumulator into a scalar,
69
+ then folds it to 0/1 via `~((diff | -diff) >> 63) & 1`. The only remaining
70
+ observable control flow is the outer length check, which operates on non-secret
71
+ input.
72
+
73
+ **Zero-copy design.** The WASM module has no internal staging buffers. The
74
+ wrapper writes `a` into WASM linear memory at offset 0 and `b` immediately
75
+ after at offset `a.length`, then passes those two offsets directly to the
76
+ `compare` function. No data is duplicated inside the WASM binary; the
77
+ comparison reads in-place from the caller-owned positions. This eliminates an
78
+ entire class of residual-data risk: there is no intermediate copy that outlives
79
+ the operation.
80
+
81
+ **Guaranteed memory zeroing.** After every comparison, the wrapper clears both
82
+ input regions via `mem.fill(0, 0, a.length * 2)` inside a `finally` block. The
83
+ wipe runs whether the comparison succeeds, throws, or is interrupted. Sensitive
84
+ material written into WASM linear memory does not persist past the end of the
85
+ call.
86
+
87
+ WASM SIMD JITs do not guarantee cycle-level constant time; the branch-free tail
88
+ is best-effort hardening against branch-prediction side channels. For
89
+ defence-in-depth against timing attacks on tag comparisons, pair this primitive
90
+ with an authenticated construction (`SerpentCipher`, `XChaCha20Cipher`) that
91
+ terminates on mismatch with a generic error message.
92
+
93
+ Maximum input size is [`CT_MAX_BYTES`](#ct_max_bytes) (32768 bytes) per side.
94
+ Throws `RangeError` if either array exceeds this limit.
95
+
96
+ See [asm_ct.md](./asm_ct.md) for the full WASM module reference, including the
97
+ SIMD algorithm, reduction technique, instantiation model, and memory layout.
98
+
99
+ Use this function when working with lower-level unauthenticated primitives or
100
+ building custom authenticated protocols on top of the hashing and KDF APIs.
101
+ Common cases:
102
+
103
+ - **Encrypt-then-MAC with `SerpentCbc` or `SerpentCtr`.** If you use the
104
+ `dangerUnauthenticated` primitive directly and compute your own HMAC-SHA256
105
+ tag, compare that tag with `constantTimeEqual`. See the [example
106
+ below](#encrypt-then-mac-with-serpentcbc).
107
+ - **Argon2id key verification.** When re-deriving an Argon2id hash to verify a
108
+ passphrase, the final comparison must be constant-time. See
109
+ [argon2id.md](./argon2id.md#password-hashing-and-verification) for the full
110
+ example.
111
+ - **Custom HMAC protocols.** Any protocol where you derive a MAC with
112
+ `HMAC_SHA256` or `HMAC_SHA512` and compare it against a received value. See
113
+ [examples.md](./examples.md#hmac-sha256-message-authentication) for a
114
+ complete example.
39
115
 
40
116
  ---
41
117
 
42
- ### bytesToHex
118
+ ### CT_MAX_BYTES
43
119
 
44
120
  ```typescript
45
- bytesToHex(bytes: Uint8Array): string
121
+ const CT_MAX_BYTES: 32768
46
122
  ```
47
123
 
48
- Converts a `Uint8Array` to a lowercase hex string (no prefix).
49
-
50
- ---
124
+ Maximum input size accepted by [`constantTimeEqual`](#constanttimeequal) per
125
+ side, in bytes. Reflects the physical layout of the WASM comparison module: one
126
+ 64 KiB page of linear memory split equally between the two input buffers (32
127
+ KiB each).
51
128
 
52
- ### utf8ToBytes
129
+ In practice the largest comparison performed anywhere in this library is a
130
+ 32-byte HMAC-SHA-256 tag. This limit only matters for custom protocols that
131
+ compare unusually large values. Use this constant to guard your own inputs
132
+ rather than hardcoding the magic number:
53
133
 
54
134
  ```typescript
55
- utf8ToBytes(str: string): Uint8Array
56
- ```
135
+ import { constantTimeEqual, CT_MAX_BYTES } from 'leviathan-crypto'
57
136
 
58
- Encodes a JavaScript string as UTF-8 bytes using the platform `TextEncoder`.
137
+ if (a.length > CT_MAX_BYTES || b.length > CT_MAX_BYTES) {
138
+ throw new RangeError(`comparison input exceeds CT_MAX_BYTES (${CT_MAX_BYTES})`)
139
+ }
140
+ const match = constantTimeEqual(a, b)
141
+ ```
59
142
 
60
143
  ---
61
144
 
62
- ### bytesToUtf8
145
+ ### hexToBytes
63
146
 
64
147
  ```typescript
65
- bytesToUtf8(bytes: Uint8Array): string
148
+ hexToBytes(hex: string): Uint8Array
66
149
  ```
67
150
 
68
- Decodes UTF-8 bytes to a JavaScript string using the platform `TextDecoder`.
151
+ Converts a hex string to a `Uint8Array`. Accepts lowercase or uppercase
152
+ characters. An optional `0x` or `0X` prefix is stripped automatically. Throws
153
+ `RangeError` on odd-length or non-hex input.
69
154
 
70
155
  ---
71
156
 
72
- ### base64ToBytes
157
+ ### bytesToHex
73
158
 
74
159
  ```typescript
75
- base64ToBytes(b64: string): Uint8Array | undefined
160
+ bytesToHex(bytes: Uint8Array): string
76
161
  ```
77
162
 
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).
163
+ Converts a `Uint8Array` to a lowercase hex string (no prefix).
79
164
 
80
165
  ---
81
166
 
82
- ### bytesToBase64
167
+ ### utf8ToBytes
83
168
 
84
169
  ```typescript
85
- bytesToBase64(bytes: Uint8Array, url?: boolean): string
170
+ utf8ToBytes(str: string): Uint8Array
86
171
  ```
87
172
 
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.
173
+ Encodes a JavaScript string as UTF-8 bytes using the platform `TextEncoder`.
89
174
 
90
175
  ---
91
176
 
92
- ### constantTimeEqual
177
+ ### bytesToUtf8
93
178
 
94
179
  ```typescript
95
- constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean
180
+ bytesToUtf8(bytes: Uint8Array): string
96
181
  ```
97
182
 
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.
183
+ Decodes UTF-8 bytes to a JavaScript string using the platform `TextDecoder`.
111
184
 
112
185
  ---
113
186
 
114
- ### CT_MAX_BYTES
187
+ ### base64ToBytes
115
188
 
116
189
  ```typescript
117
- const CT_MAX_BYTES: 32768
190
+ base64ToBytes(b64: string): Uint8Array
118
191
  ```
119
192
 
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).
193
+ Decodes a base64 or base64url string to a `Uint8Array`. Handles padded,
194
+ unpadded, and legacy `%3d` padding. Unpadded base64url input is accepted (RFC
195
+ 4648 §5). Throws `RangeError` if the input is not valid base64 (illegal
196
+ characters or `rem=1` length).
121
197
 
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:
198
+ ---
123
199
 
124
- ```typescript
125
- import { constantTimeEqual, CT_MAX_BYTES } from 'leviathan-crypto'
200
+ ### bytesToBase64
126
201
 
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)
202
+ ```typescript
203
+ bytesToBase64(bytes: Uint8Array, url?: boolean): string
131
204
  ```
132
205
 
206
+ Encodes a `Uint8Array` to a base64 string. Pass `url = true` for base64url (RFC
207
+ 4648 §5), which uses `-` and `_` instead of `+` and `/` with no padding
208
+ characters. Defaults to standard base64.
209
+
133
210
  ---
134
211
 
135
212
  ### wipe
@@ -138,7 +215,8 @@ const match = constantTimeEqual(a, b)
138
215
  wipe(data: Uint8Array | Uint16Array | Uint32Array): void
139
216
  ```
140
217
 
141
- Zeroes a typed array in-place by calling `fill(0)`. Use this to clear keys, plaintext, or any sensitive material when you are done with it.
218
+ Zeroes a typed array in-place by calling `fill(0)`. Use this to clear keys,
219
+ plaintext, or any sensitive material when you are done with it.
142
220
 
143
221
  ---
144
222
 
@@ -148,7 +226,8 @@ Zeroes a typed array in-place by calling `fill(0)`. Use this to clear keys, plai
148
226
  xor(a: Uint8Array, b: Uint8Array): Uint8Array
149
227
  ```
150
228
 
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.
229
+ Returns a new `Uint8Array` where each byte is `a[i] ^ b[i]`. Both arrays must
230
+ have the same length. Throws `RangeError` if they differ.
152
231
 
153
232
  ---
154
233
 
@@ -168,7 +247,8 @@ Concatenates one or more `Uint8Array`s into a new array.
168
247
  randomBytes(n: number): Uint8Array
169
248
  ```
170
249
 
171
- Returns `n` cryptographically secure random bytes via the Web Crypto API (`crypto.getRandomValues`).
250
+ Returns `n` cryptographically secure random bytes via the Web Crypto API
251
+ (`crypto.getRandomValues`).
172
252
 
173
253
  ---
174
254
 
@@ -182,13 +262,12 @@ Returns `true` if the current runtime supports WebAssembly SIMD (the `v128`
182
262
  type and associated operations). The result is computed once on first call by
183
263
  validating a minimal v128 WASM module, then cached for subsequent calls.
184
264
 
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.
265
+ A public utility for consumers who want to feature-detect before calling
266
+ `init()`. The library requires SIMD for `serpent`, `chacha20`, and
267
+ `constantTimeEqual`; calls into those modules throw a branded error on
268
+ non-SIMD runtimes. `sha2` and `sha3` do not require SIMD.
189
269
 
190
- Supported in all modern browsers and Node.js 16+. Returns `false` in older
191
- environments, which fall back silently to the scalar path.
270
+ Supported in all modern browsers and Node.js 16+.
192
271
 
193
272
  ---
194
273
 
@@ -229,14 +308,17 @@ console.log(b64url) // "bGV2aWF0aGFuLWNyeXB0bw"
229
308
 
230
309
  // Decoding (accepts both standard and url variants)
231
310
  const decoded = base64ToBytes(b64)
232
- if (decoded) console.log(bytesToUtf8(decoded)) // "leviathan-crypto"
311
+ console.log(bytesToUtf8(decoded)) // "leviathan-crypto"
233
312
  ```
234
313
 
235
314
  ---
236
315
 
237
316
  ### Encrypt-then-MAC with SerpentCbc
238
317
 
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.
318
+ If you use `SerpentCbc` or `SerpentCtr` directly with `{ dangerUnauthenticated:
319
+ true }`, you are responsible for authentication. The correct pattern is
320
+ Encrypt-then-MAC: encrypt first, then compute HMAC-SHA256 over the ciphertext,
321
+ and use `constantTimeEqual` to verify on decrypt.
240
322
 
241
323
  ```typescript
242
324
  import {
@@ -288,7 +370,10 @@ wipe(expectedTag)
288
370
  ```
289
371
 
290
372
  > [!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.
373
+ > `Seal` with `SerpentCipher` does all of this for you; key derivation, IV
374
+ > handling, Encrypt-then-MAC, and constant-time verification, with no manual
375
+ > steps. The pattern above is only relevant if you need direct access to the
376
+ > raw `SerpentCbc` primitive.
292
377
 
293
378
  ---
294
379
 
@@ -343,8 +428,8 @@ console.log(combined.length) // 32
343
428
  | Function | Condition | Behavior |
344
429
  |---|---|---|
345
430
  | `hexToBytes` | Odd-length string | Throws `RangeError` |
346
- | `hexToBytes` | Invalid hex characters | Bytes decode as `NaN` -> `0` |
347
- | `base64ToBytes` | Invalid length or characters | Returns `undefined` |
431
+ | `hexToBytes` | Invalid hex characters | Throws `RangeError` |
432
+ | `base64ToBytes` | Invalid length or characters | Throws `RangeError` |
348
433
  | `constantTimeEqual` | Arrays differ in length | Returns `false` immediately |
349
434
  | `constantTimeEqual` | Either array exceeds `CT_MAX_BYTES` | Throws `RangeError` |
350
435
  | `xor` | Arrays differ in length | Throws `RangeError` |
@@ -353,15 +438,20 @@ console.log(combined.length) // 32
353
438
 
354
439
  ---
355
440
 
356
- > ## Cross-References
357
- >
358
- > - [index](./README.md) — Project Documentation index
359
- > - [architecture](./architecture.md) architecture overview, module relationships, buffer layouts, and build pipeline
360
- > - [serpent](./serpent.md) Serpent modes consume keys from `randomBytes`; wrappers use `wipe` and `constantTimeEqual`
361
- > - [chacha20](./chacha20.md) ChaCha20/Poly1305 classes use `randomBytes` for nonce generation
362
- > - [sha2](./sha2.md) SHA-2 and HMAC classes; output often converted with `bytesToHex`
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`
366
- > - [types](./types.md) public interfaces whose implementations rely on these utilities
367
- > - [test-suite](./test-suite.md) test suite structure and vector corpus
441
+
442
+ ## Cross-References
443
+
444
+ | Document | Description |
445
+ | -------- | ----------- |
446
+ | [index](./README.md) | Project Documentation index |
447
+ | [architecture](./architecture.md) | architecture overview, module relationships, buffer layouts, and build pipeline |
448
+ | [asm_ct](./asm_ct.md) | WASM module reference for `constantTimeEqual`: SIMD algorithm, zero-copy layout, instantiation model, and memory zeroing |
449
+ | [serpent](./serpent.md) | Serpent modes consume keys from `randomBytes`; wrappers use `wipe` and `constantTimeEqual` |
450
+ | [chacha20](./chacha20.md) | ChaCha20/Poly1305 classes use `randomBytes` for nonce generation |
451
+ | [sha2](./sha2.md) | SHA-2 and HMAC classes; output often converted with `bytesToHex` |
452
+ | [sha3](./sha3.md) | SHA-3 and SHAKE classes; output often converted with `bytesToHex` |
453
+ | [argon2id](./argon2id.md) | passphrase-based encryption; uses `constantTimeEqual` for hash verification |
454
+ | [examples](./examples.md) | full HMAC-SHA256 custom protocol example using `constantTimeEqual` |
455
+ | [types](./types.md) | public interfaces whose implementations rely on these utilities |
456
+ | [test-suite](./test-suite.md) | test suite structure and vector corpus |
457
+
@@ -0,0 +1 @@
1
+ export declare const WORKER_SOURCE = "\"use strict\";(()=>{var U=new Uint8Array([0,97,115,109,1,0,0,0,1,8,1,96,3,127,127,127,1,127,3,2,1,0,5,4,1,1,1,1,7,20,2,7,99,111,109,112,97,114,101,0,0,6,109,101,109,111,114,121,2,0,10,133,1,1,130,1,3,2,127,1,126,1,123,3,64,32,3,65,16,106,34,4,32,2,76,4,64,32,6,32,0,32,3,106,253,0,4,0,32,1,32,3,106,253,0,4,0,253,81,253,80,33,6,32,4,33,3,12,1,11,11,3,64,32,2,32,3,74,4,64,32,5,32,0,32,3,106,49,0,0,32,1,32,3,106,49,0,0,133,132,33,5,32,3,65,1,106,33,3,12,1,11,11,66,0,32,5,32,6,253,29,0,32,6,253,29,1,132,132,34,5,125,32,5,132,66,63,135,66,127,133,167,65,1,113,11]);var v=null,k=null,O=!1,A=null,T=32768;function P(){if(O){if(A)throw A;return}if(O=!0,!R())throw A=new Error(\"leviathan-crypto: constantTimeEqual requires WebAssembly SIMD \\u2014 this runtime does not support it\"),A;try{let t=U.buffer.slice(U.byteOffset,U.byteOffset+U.byteLength),r=new WebAssembly.Module(t),e=new WebAssembly.Instance(r).exports;k=e.memory,v=e.compare}catch(t){throw A=new Error(`leviathan-crypto: ct WASM module failed to instantiate: ${t.message}`),A}}var B=(t,r)=>{if(t.length!==r.length)return!1;if(t.length>T)throw new RangeError(`constantTimeEqual: max ${T} bytes (got ${t.length})`);P();let n=k,e=v;if(!n||!e)throw new Error(\"leviathan-crypto: ct init invariant violated\");let o=new Uint8Array(n.buffer);o.set(t,0),o.set(r,t.length);try{return e(0,t.length,t.length)===1}finally{o.fill(0,0,t.length*2)}};var m=null;function R(){if(m!==null)return m;if(typeof WebAssembly>\"u\"||typeof WebAssembly.validate!=\"function\")return m=!1,m;try{m=WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11]))}catch{m=!1}return m}var d=class t extends Error{constructor(r){super(`${r}: authentication failed`),this.name=\"AuthenticationError\",Object.setPrototypeOf(this,t.prototype)}};function l(t,r){if(r.length===0)return;let n=new Uint8Array(t.memory.buffer),e=t.getPolyMsgOffset(),o=0;for(;o<r.length;){let a=Math.min(64,r.length-o);n.set(r.subarray(o,o+a),e),t.polyUpdate(a),o+=a}}function M(t,r){let n=new Uint8Array(16),e=new DataView(n.buffer);return e.setUint32(0,t>>>0,!0),e.setUint32(4,Math.floor(t/4294967296)>>>0,!0),e.setUint32(8,r>>>0,!0),e.setUint32(12,Math.floor(r/4294967296)>>>0,!0),n}function W(t,r,n,e,o){let a=t.getChunkSize();if(e.length>a)throw new RangeError(`plaintext exceeds ${a} bytes \\u2014 split into smaller chunks`);let u=new Uint8Array(t.memory.buffer);u.set(r,t.getKeyOffset()),u.set(n,t.getChachaNonceOffset()),t.chachaLoadKey(),t.chachaGenPolyKey(),t.polyInit(),l(t,o);let f=(16-o.length%16)%16;f>0&&l(t,new Uint8Array(f)),t.chachaSetCounter(1),u.set(e,t.getChunkPtOffset()),t.chachaEncryptChunk_simd(e.length);let i=t.getChunkCtOffset(),s=new Uint8Array(t.memory.buffer).slice(i,i+e.length);l(t,s);let g=(16-e.length%16)%16;g>0&&l(t,new Uint8Array(g)),l(t,M(o.length,e.length)),t.polyFinal();let c=t.getPolyTagOffset(),p=new Uint8Array(t.memory.buffer).slice(c,c+16);return{ciphertext:s,tag:p}}function K(t,r,n,e,o,a,u=\"chacha20-poly1305\"){let f=t.getChunkSize();if(e.length>f)throw new RangeError(`ciphertext exceeds ${f} bytes \\u2014 split into smaller chunks`);let i=new Uint8Array(t.memory.buffer);i.set(r,t.getKeyOffset()),i.set(n,t.getChachaNonceOffset()),t.chachaLoadKey(),t.chachaGenPolyKey(),t.polyInit(),l(t,a);let s=(16-a.length%16)%16;s>0&&l(t,new Uint8Array(s)),l(t,e);let g=(16-e.length%16)%16;g>0&&l(t,new Uint8Array(g)),l(t,M(a.length,e.length)),t.polyFinal();let c=t.getPolyTagOffset(),p=new Uint8Array(t.memory.buffer).slice(c,c+16);if(!B(p,o)){let b=t.getChunkCtOffset();i.fill(0,b,b+f);let C=t.getChachaBlockOffset();i.fill(0,C,C+64);let E=t.getPolyKeyOffset();throw i.fill(0,E,E+32),new d(u)}t.chachaSetCounter(1),t.chachaLoadKey(),new Uint8Array(t.memory.buffer).set(e,t.getChunkPtOffset()),t.chachaEncryptChunk_simd(e.length);let w=t.getChunkCtOffset();return new Uint8Array(t.memory.buffer).slice(w,w+e.length)}var y,h;self.onmessage=async t=>{let r=t.data;if(r.type===\"init\"){try{let n=new WebAssembly.Memory({initial:3,maximum:3}),e=r.modules.chacha20;if(y=(await WebAssembly.instantiate(e,{env:{memory:n}})).exports,h=new Uint8Array(r.derivedKeyBytes),h.length!==32)throw new Error(`expected 32 derived key bytes (got ${h.length})`);r.derivedKeyBytes.fill(0),self.postMessage({type:\"ready\"})}catch(n){self.postMessage({type:\"error\",id:-1,message:n.message,isAuthError:!1})}return}if(r.type===\"wipe\"){h&&h.fill(0),h=void 0,y&&y.wipeBuffers(),y=void 0,self.postMessage({type:\"wiped\"});return}if(!y||!h){self.postMessage({type:\"error\",id:r.id,message:\"worker not initialized\",isAuthError:!1});return}try{let{id:n,op:e,counterNonce:o,data:a,aad:u}=r,f=u??new Uint8Array(0),i=r.derivedKeyBytes??h,s;if(e===\"seal\"){let{ciphertext:c,tag:p}=W(y,i,o,a,f);s=new Uint8Array(c.length+16),s.set(c),s.set(p,c.length)}else{let c=a.subarray(0,a.length-16),p=a.subarray(a.length-16);s=K(y,i,o,c,p,f,\"xchacha20-poly1305\")}let g=s.buffer instanceof ArrayBuffer?[s.buffer]:[];self.postMessage({type:\"result\",id:n,data:s},{transfer:g})}catch(n){let e=n instanceof d;self.postMessage({type:\"error\",id:r.id,message:n.message,cipher:e?\"xchacha20-poly1305\":void 0,isAuthError:e})}finally{r.derivedKeyBytes&&r.derivedKeyBytes.fill(0),y&&y.wipeBuffers()}};})();\n";
@@ -0,0 +1,5 @@
1
+ // Generated by scripts/embed-workers.ts — do not edit
2
+ // IIFE-bundled pool worker source. Spawned via blob URL by
3
+ // the cipher-suite.ts createPoolWorker; consumers do not
4
+ // import this directly.
5
+ export const WORKER_SOURCE = "\"use strict\";(()=>{var U=new Uint8Array([0,97,115,109,1,0,0,0,1,8,1,96,3,127,127,127,1,127,3,2,1,0,5,4,1,1,1,1,7,20,2,7,99,111,109,112,97,114,101,0,0,6,109,101,109,111,114,121,2,0,10,133,1,1,130,1,3,2,127,1,126,1,123,3,64,32,3,65,16,106,34,4,32,2,76,4,64,32,6,32,0,32,3,106,253,0,4,0,32,1,32,3,106,253,0,4,0,253,81,253,80,33,6,32,4,33,3,12,1,11,11,3,64,32,2,32,3,74,4,64,32,5,32,0,32,3,106,49,0,0,32,1,32,3,106,49,0,0,133,132,33,5,32,3,65,1,106,33,3,12,1,11,11,66,0,32,5,32,6,253,29,0,32,6,253,29,1,132,132,34,5,125,32,5,132,66,63,135,66,127,133,167,65,1,113,11]);var v=null,k=null,O=!1,A=null,T=32768;function P(){if(O){if(A)throw A;return}if(O=!0,!R())throw A=new Error(\"leviathan-crypto: constantTimeEqual requires WebAssembly SIMD \\u2014 this runtime does not support it\"),A;try{let t=U.buffer.slice(U.byteOffset,U.byteOffset+U.byteLength),r=new WebAssembly.Module(t),e=new WebAssembly.Instance(r).exports;k=e.memory,v=e.compare}catch(t){throw A=new Error(`leviathan-crypto: ct WASM module failed to instantiate: ${t.message}`),A}}var B=(t,r)=>{if(t.length!==r.length)return!1;if(t.length>T)throw new RangeError(`constantTimeEqual: max ${T} bytes (got ${t.length})`);P();let n=k,e=v;if(!n||!e)throw new Error(\"leviathan-crypto: ct init invariant violated\");let o=new Uint8Array(n.buffer);o.set(t,0),o.set(r,t.length);try{return e(0,t.length,t.length)===1}finally{o.fill(0,0,t.length*2)}};var m=null;function R(){if(m!==null)return m;if(typeof WebAssembly>\"u\"||typeof WebAssembly.validate!=\"function\")return m=!1,m;try{m=WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11]))}catch{m=!1}return m}var d=class t extends Error{constructor(r){super(`${r}: authentication failed`),this.name=\"AuthenticationError\",Object.setPrototypeOf(this,t.prototype)}};function l(t,r){if(r.length===0)return;let n=new Uint8Array(t.memory.buffer),e=t.getPolyMsgOffset(),o=0;for(;o<r.length;){let a=Math.min(64,r.length-o);n.set(r.subarray(o,o+a),e),t.polyUpdate(a),o+=a}}function M(t,r){let n=new Uint8Array(16),e=new DataView(n.buffer);return e.setUint32(0,t>>>0,!0),e.setUint32(4,Math.floor(t/4294967296)>>>0,!0),e.setUint32(8,r>>>0,!0),e.setUint32(12,Math.floor(r/4294967296)>>>0,!0),n}function W(t,r,n,e,o){let a=t.getChunkSize();if(e.length>a)throw new RangeError(`plaintext exceeds ${a} bytes \\u2014 split into smaller chunks`);let u=new Uint8Array(t.memory.buffer);u.set(r,t.getKeyOffset()),u.set(n,t.getChachaNonceOffset()),t.chachaLoadKey(),t.chachaGenPolyKey(),t.polyInit(),l(t,o);let f=(16-o.length%16)%16;f>0&&l(t,new Uint8Array(f)),t.chachaSetCounter(1),u.set(e,t.getChunkPtOffset()),t.chachaEncryptChunk_simd(e.length);let i=t.getChunkCtOffset(),s=new Uint8Array(t.memory.buffer).slice(i,i+e.length);l(t,s);let g=(16-e.length%16)%16;g>0&&l(t,new Uint8Array(g)),l(t,M(o.length,e.length)),t.polyFinal();let c=t.getPolyTagOffset(),p=new Uint8Array(t.memory.buffer).slice(c,c+16);return{ciphertext:s,tag:p}}function K(t,r,n,e,o,a,u=\"chacha20-poly1305\"){let f=t.getChunkSize();if(e.length>f)throw new RangeError(`ciphertext exceeds ${f} bytes \\u2014 split into smaller chunks`);let i=new Uint8Array(t.memory.buffer);i.set(r,t.getKeyOffset()),i.set(n,t.getChachaNonceOffset()),t.chachaLoadKey(),t.chachaGenPolyKey(),t.polyInit(),l(t,a);let s=(16-a.length%16)%16;s>0&&l(t,new Uint8Array(s)),l(t,e);let g=(16-e.length%16)%16;g>0&&l(t,new Uint8Array(g)),l(t,M(a.length,e.length)),t.polyFinal();let c=t.getPolyTagOffset(),p=new Uint8Array(t.memory.buffer).slice(c,c+16);if(!B(p,o)){let b=t.getChunkCtOffset();i.fill(0,b,b+f);let C=t.getChachaBlockOffset();i.fill(0,C,C+64);let E=t.getPolyKeyOffset();throw i.fill(0,E,E+32),new d(u)}t.chachaSetCounter(1),t.chachaLoadKey(),new Uint8Array(t.memory.buffer).set(e,t.getChunkPtOffset()),t.chachaEncryptChunk_simd(e.length);let w=t.getChunkCtOffset();return new Uint8Array(t.memory.buffer).slice(w,w+e.length)}var y,h;self.onmessage=async t=>{let r=t.data;if(r.type===\"init\"){try{let n=new WebAssembly.Memory({initial:3,maximum:3}),e=r.modules.chacha20;if(y=(await WebAssembly.instantiate(e,{env:{memory:n}})).exports,h=new Uint8Array(r.derivedKeyBytes),h.length!==32)throw new Error(`expected 32 derived key bytes (got ${h.length})`);r.derivedKeyBytes.fill(0),self.postMessage({type:\"ready\"})}catch(n){self.postMessage({type:\"error\",id:-1,message:n.message,isAuthError:!1})}return}if(r.type===\"wipe\"){h&&h.fill(0),h=void 0,y&&y.wipeBuffers(),y=void 0,self.postMessage({type:\"wiped\"});return}if(!y||!h){self.postMessage({type:\"error\",id:r.id,message:\"worker not initialized\",isAuthError:!1});return}try{let{id:n,op:e,counterNonce:o,data:a,aad:u}=r,f=u??new Uint8Array(0),i=r.derivedKeyBytes??h,s;if(e===\"seal\"){let{ciphertext:c,tag:p}=W(y,i,o,a,f);s=new Uint8Array(c.length+16),s.set(c),s.set(p,c.length)}else{let c=a.subarray(0,a.length-16),p=a.subarray(a.length-16);s=K(y,i,o,c,p,f,\"xchacha20-poly1305\")}let g=s.buffer instanceof ArrayBuffer?[s.buffer]:[];self.postMessage({type:\"result\",id:n,data:s},{transfer:g})}catch(n){let e=n instanceof d;self.postMessage({type:\"error\",id:r.id,message:n.message,cipher:e?\"xchacha20-poly1305\":void 0,isAuthError:e})}finally{r.derivedKeyBytes&&r.derivedKeyBytes.fill(0),y&&y.wipeBuffers()}};})();\n";