leviathan-crypto 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CLAUDE.md +86 -443
  2. package/README.md +198 -65
  3. package/dist/aes/aes-cbc.d.ts +40 -0
  4. package/dist/aes/aes-cbc.js +158 -0
  5. package/dist/aes/aes-ctr.d.ts +50 -0
  6. package/dist/aes/aes-ctr.js +141 -0
  7. package/dist/aes/aes-gcm-siv.d.ts +67 -0
  8. package/dist/aes/aes-gcm-siv.js +217 -0
  9. package/dist/aes/aes-gcm.d.ts +61 -0
  10. package/dist/aes/aes-gcm.js +226 -0
  11. package/dist/aes/cipher-suite.d.ts +21 -0
  12. package/dist/aes/cipher-suite.js +179 -0
  13. package/dist/aes/embedded.d.ts +1 -0
  14. package/dist/aes/embedded.js +26 -0
  15. package/dist/aes/generator.d.ts +14 -0
  16. package/dist/aes/generator.js +103 -0
  17. package/dist/aes/index.d.ts +58 -0
  18. package/dist/aes/index.js +125 -0
  19. package/dist/aes/ops.d.ts +60 -0
  20. package/dist/aes/ops.js +164 -0
  21. package/dist/aes/pool-worker.d.ts +1 -0
  22. package/dist/aes/pool-worker.js +92 -0
  23. package/dist/aes/types.d.ts +1 -0
  24. package/dist/aes/types.js +23 -0
  25. package/dist/aes.wasm +0 -0
  26. package/dist/blake3/embedded.d.ts +1 -0
  27. package/dist/blake3/embedded.js +26 -0
  28. package/dist/blake3/index.d.ts +143 -0
  29. package/dist/blake3/index.js +620 -0
  30. package/dist/blake3/types.d.ts +102 -0
  31. package/dist/blake3/types.js +31 -0
  32. package/dist/blake3/validate.d.ts +29 -0
  33. package/dist/blake3/validate.js +80 -0
  34. package/dist/blake3.wasm +0 -0
  35. package/dist/chacha20/cipher-suite.js +47 -25
  36. package/dist/chacha20/generator.d.ts +2 -2
  37. package/dist/chacha20/generator.js +4 -4
  38. package/dist/chacha20/index.d.ts +16 -15
  39. package/dist/chacha20/index.js +52 -46
  40. package/dist/chacha20/ops.d.ts +7 -7
  41. package/dist/chacha20/ops.js +34 -34
  42. package/dist/chacha20/pool-worker.js +5 -3
  43. package/dist/cte-wasm.d.ts +1 -0
  44. package/dist/cte-wasm.js +3 -0
  45. package/dist/curve25519.wasm +0 -0
  46. package/dist/ecdsa/der.d.ts +23 -0
  47. package/dist/ecdsa/der.js +192 -0
  48. package/dist/ecdsa/ecprivatekey-der.d.ts +32 -0
  49. package/dist/ecdsa/ecprivatekey-der.js +230 -0
  50. package/dist/ecdsa/embedded.d.ts +1 -0
  51. package/dist/ecdsa/embedded.js +25 -0
  52. package/dist/ecdsa/index.d.ts +124 -0
  53. package/dist/ecdsa/index.js +366 -0
  54. package/dist/ecdsa/types.d.ts +31 -0
  55. package/dist/ecdsa/types.js +28 -0
  56. package/dist/ecdsa/validate.d.ts +18 -0
  57. package/dist/ecdsa/validate.js +92 -0
  58. package/dist/ed25519/embedded.d.ts +1 -0
  59. package/dist/ed25519/embedded.js +31 -0
  60. package/dist/ed25519/index.d.ts +70 -0
  61. package/dist/ed25519/index.js +308 -0
  62. package/dist/ed25519/types.d.ts +27 -0
  63. package/dist/ed25519/types.js +27 -0
  64. package/dist/ed25519/validate.d.ts +7 -0
  65. package/dist/ed25519/validate.js +77 -0
  66. package/dist/embedded/aes-pool-worker.d.ts +1 -0
  67. package/dist/embedded/aes-pool-worker.js +5 -0
  68. package/dist/embedded/aes.d.ts +1 -0
  69. package/dist/embedded/aes.js +3 -0
  70. package/dist/embedded/blake3.d.ts +1 -0
  71. package/dist/embedded/blake3.js +3 -0
  72. package/dist/embedded/chacha20-pool-worker.d.ts +1 -1
  73. package/dist/embedded/chacha20-pool-worker.js +2 -2
  74. package/dist/embedded/chacha20.d.ts +1 -1
  75. package/dist/embedded/chacha20.js +2 -2
  76. package/dist/embedded/curve25519.d.ts +1 -0
  77. package/dist/embedded/curve25519.js +3 -0
  78. package/dist/embedded/mldsa.d.ts +1 -0
  79. package/dist/embedded/mldsa.js +3 -0
  80. package/dist/embedded/mlkem.d.ts +1 -0
  81. package/dist/embedded/mlkem.js +3 -0
  82. package/dist/embedded/p256.d.ts +1 -0
  83. package/dist/embedded/p256.js +3 -0
  84. package/dist/embedded/serpent-pool-worker.d.ts +1 -1
  85. package/dist/embedded/serpent-pool-worker.js +2 -2
  86. package/dist/embedded/serpent.d.ts +1 -1
  87. package/dist/embedded/serpent.js +2 -2
  88. package/dist/embedded/sha2.d.ts +1 -1
  89. package/dist/embedded/sha2.js +2 -2
  90. package/dist/embedded/sha3.d.ts +1 -1
  91. package/dist/embedded/sha3.js +2 -2
  92. package/dist/embedded/slhdsa.d.ts +1 -0
  93. package/dist/embedded/slhdsa.js +3 -0
  94. package/dist/errors.d.ts +92 -1
  95. package/dist/errors.js +111 -1
  96. package/dist/fortuna.d.ts +5 -5
  97. package/dist/fortuna.js +37 -64
  98. package/dist/index.d.ts +38 -9
  99. package/dist/index.js +63 -19
  100. package/dist/init.d.ts +1 -1
  101. package/dist/init.js +11 -25
  102. package/dist/keccak/embedded.js +1 -1
  103. package/dist/keccak/index.d.ts +2 -0
  104. package/dist/keccak/index.js +4 -2
  105. package/dist/loader.d.ts +1 -24
  106. package/dist/loader.js +13 -16
  107. package/dist/merkle/blake3-tree.d.ts +35 -0
  108. package/dist/merkle/blake3-tree.js +187 -0
  109. package/dist/merkle/checkpoint.d.ts +58 -0
  110. package/dist/merkle/checkpoint.js +217 -0
  111. package/dist/merkle/index.d.ts +19 -0
  112. package/dist/merkle/index.js +37 -0
  113. package/dist/merkle/merkle-log.d.ts +130 -0
  114. package/dist/merkle/merkle-log.js +207 -0
  115. package/dist/merkle/merkle-verifier.d.ts +126 -0
  116. package/dist/merkle/merkle-verifier.js +296 -0
  117. package/dist/merkle/proof.d.ts +70 -0
  118. package/dist/merkle/proof.js +300 -0
  119. package/dist/merkle/sha256-tree.d.ts +33 -0
  120. package/dist/merkle/sha256-tree.js +145 -0
  121. package/dist/merkle/signed-log.d.ts +156 -0
  122. package/dist/merkle/signed-log.js +356 -0
  123. package/dist/merkle/signed-note.d.ts +309 -0
  124. package/dist/merkle/signed-note.js +648 -0
  125. package/dist/merkle/sth.d.ts +31 -0
  126. package/dist/merkle/sth.js +31 -0
  127. package/dist/merkle/storage.d.ts +40 -0
  128. package/dist/merkle/storage.js +71 -0
  129. package/dist/merkle/tree.d.ts +68 -0
  130. package/dist/merkle/tree.js +94 -0
  131. package/dist/mldsa/embedded.d.ts +1 -0
  132. package/dist/{kyber → mldsa}/embedded.js +5 -5
  133. package/dist/mldsa/expand.d.ts +53 -0
  134. package/dist/mldsa/expand.js +188 -0
  135. package/dist/mldsa/format.d.ts +16 -0
  136. package/dist/mldsa/format.js +68 -0
  137. package/dist/mldsa/hashvariant.d.ts +32 -0
  138. package/dist/mldsa/hashvariant.js +248 -0
  139. package/dist/mldsa/index.d.ts +142 -0
  140. package/dist/mldsa/index.js +463 -0
  141. package/dist/mldsa/keygen.d.ts +16 -0
  142. package/dist/mldsa/keygen.js +232 -0
  143. package/dist/mldsa/params.d.ts +21 -0
  144. package/dist/mldsa/params.js +55 -0
  145. package/dist/mldsa/sha3-helpers.d.ts +30 -0
  146. package/dist/mldsa/sha3-helpers.js +124 -0
  147. package/dist/mldsa/sign.d.ts +36 -0
  148. package/dist/mldsa/sign.js +380 -0
  149. package/dist/mldsa/types.d.ts +91 -0
  150. package/dist/mldsa/types.js +25 -0
  151. package/dist/mldsa/validate.d.ts +55 -0
  152. package/dist/mldsa/validate.js +125 -0
  153. package/dist/mldsa/verify.d.ts +29 -0
  154. package/dist/mldsa/verify.js +269 -0
  155. package/dist/mldsa.wasm +0 -0
  156. package/dist/mlkem/embedded.d.ts +1 -0
  157. package/dist/mlkem/embedded.js +27 -0
  158. package/dist/mlkem/indcpa.d.ts +49 -0
  159. package/dist/{kyber → mlkem}/indcpa.js +44 -44
  160. package/dist/mlkem/index.d.ts +37 -0
  161. package/dist/{kyber → mlkem}/index.js +24 -34
  162. package/dist/mlkem/kem.d.ts +21 -0
  163. package/dist/{kyber → mlkem}/kem.js +44 -64
  164. package/dist/{kyber → mlkem}/params.d.ts +4 -4
  165. package/dist/{kyber → mlkem}/params.js +2 -2
  166. package/dist/mlkem/suite.d.ts +12 -0
  167. package/dist/{kyber → mlkem}/suite.js +17 -12
  168. package/dist/{kyber → mlkem}/types.d.ts +3 -3
  169. package/dist/{kyber → mlkem}/types.js +1 -1
  170. package/dist/{kyber → mlkem}/validate.d.ts +7 -7
  171. package/dist/{kyber → mlkem}/validate.js +7 -7
  172. package/dist/{kyber.wasm → mlkem.wasm} +0 -0
  173. package/dist/p256.wasm +0 -0
  174. package/dist/ratchet/index.d.ts +2 -0
  175. package/dist/ratchet/index.js +1 -0
  176. package/dist/ratchet/kdf-chain.js +3 -3
  177. package/dist/ratchet/ratchet-keypair.js +2 -2
  178. package/dist/ratchet/root-kdf.js +7 -7
  179. package/dist/ratchet/skipped-key-store.js +4 -4
  180. package/dist/ratchet/types.d.ts +1 -1
  181. package/dist/serpent/cipher-suite.js +20 -17
  182. package/dist/serpent/generator.d.ts +1 -1
  183. package/dist/serpent/generator.js +2 -2
  184. package/dist/serpent/index.d.ts +8 -7
  185. package/dist/serpent/index.js +18 -27
  186. package/dist/serpent/pool-worker.js +7 -5
  187. package/dist/serpent/serpent-cbc.d.ts +4 -4
  188. package/dist/serpent/serpent-cbc.js +11 -8
  189. package/dist/serpent/shared-ops.d.ts +3 -23
  190. package/dist/serpent/shared-ops.js +50 -85
  191. package/dist/serpent.wasm +0 -0
  192. package/dist/sha2/hkdf.js +5 -5
  193. package/dist/sha2/index.d.ts +21 -1
  194. package/dist/sha2/index.js +65 -10
  195. package/dist/sha2/types.d.ts +41 -2
  196. package/dist/sha2.wasm +0 -0
  197. package/dist/sha3/index.d.ts +72 -3
  198. package/dist/sha3/index.js +240 -14
  199. package/dist/sha3/kmac.d.ts +121 -0
  200. package/dist/sha3/kmac.js +800 -0
  201. package/dist/sha3.wasm +0 -0
  202. package/dist/shared/pkcs7.d.ts +22 -0
  203. package/dist/shared/pkcs7.js +84 -0
  204. package/dist/sign/ctx.d.ts +41 -0
  205. package/dist/sign/ctx.js +102 -0
  206. package/dist/sign/envelope.d.ts +45 -0
  207. package/dist/sign/envelope.js +152 -0
  208. package/dist/sign/hasher.d.ts +9 -0
  209. package/dist/sign/hasher.js +132 -0
  210. package/dist/sign/index.d.ts +11 -0
  211. package/dist/sign/index.js +34 -0
  212. package/dist/sign/sign-stream.d.ts +25 -0
  213. package/dist/sign/sign-stream.js +112 -0
  214. package/dist/sign/suites/ecdsa-p256.d.ts +2 -0
  215. package/dist/sign/suites/ecdsa-p256.js +120 -0
  216. package/dist/sign/suites/ed25519.d.ts +3 -0
  217. package/dist/sign/suites/ed25519.js +165 -0
  218. package/dist/sign/suites/hybrid-classical.d.ts +23 -0
  219. package/dist/sign/suites/hybrid-classical.js +526 -0
  220. package/dist/sign/suites/hybrid-pq.d.ts +4 -0
  221. package/dist/sign/suites/hybrid-pq.js +234 -0
  222. package/dist/sign/suites/mldsa.d.ts +7 -0
  223. package/dist/sign/suites/mldsa.js +161 -0
  224. package/dist/sign/suites/slhdsa.d.ts +7 -0
  225. package/dist/sign/suites/slhdsa.js +176 -0
  226. package/dist/sign/types.d.ts +106 -0
  227. package/dist/sign/types.js +28 -0
  228. package/dist/sign/verify-stream.d.ts +30 -0
  229. package/dist/sign/verify-stream.js +227 -0
  230. package/dist/slhdsa/embedded.d.ts +1 -0
  231. package/dist/slhdsa/embedded.js +26 -0
  232. package/dist/slhdsa/index.d.ts +149 -0
  233. package/dist/slhdsa/index.js +493 -0
  234. package/dist/slhdsa/params.d.ts +26 -0
  235. package/dist/slhdsa/params.js +70 -0
  236. package/dist/slhdsa/prehash.d.ts +68 -0
  237. package/dist/slhdsa/prehash.js +307 -0
  238. package/dist/slhdsa/sign.d.ts +39 -0
  239. package/dist/slhdsa/sign.js +116 -0
  240. package/dist/slhdsa/types.d.ts +129 -0
  241. package/dist/slhdsa/types.js +27 -0
  242. package/dist/slhdsa/validate.d.ts +60 -0
  243. package/dist/slhdsa/validate.js +127 -0
  244. package/dist/slhdsa/verify.d.ts +32 -0
  245. package/dist/slhdsa/verify.js +107 -0
  246. package/dist/slhdsa.wasm +0 -0
  247. package/dist/stream/header.js +3 -3
  248. package/dist/stream/index.d.ts +1 -0
  249. package/dist/stream/index.js +1 -0
  250. package/dist/stream/open-stream.js +31 -10
  251. package/dist/stream/seal-stream-pool.d.ts +1 -0
  252. package/dist/stream/seal-stream-pool.js +63 -26
  253. package/dist/stream/seal-stream.d.ts +1 -1
  254. package/dist/stream/seal-stream.js +20 -9
  255. package/dist/stream/seal.js +6 -6
  256. package/dist/stream/types.d.ts +3 -1
  257. package/dist/stream/types.js +1 -1
  258. package/dist/types.d.ts +1 -1
  259. package/dist/types.js +1 -1
  260. package/dist/utils.d.ts +3 -3
  261. package/dist/utils.js +46 -54
  262. package/dist/wasm-source.d.ts +7 -7
  263. package/dist/wasm-source.js +1 -1
  264. package/dist/x25519/embedded.d.ts +1 -0
  265. package/dist/x25519/embedded.js +31 -0
  266. package/dist/x25519/index.d.ts +43 -0
  267. package/dist/x25519/index.js +159 -0
  268. package/dist/x25519/types.d.ts +25 -0
  269. package/dist/x25519/types.js +27 -0
  270. package/dist/x25519/validate.d.ts +2 -0
  271. package/dist/x25519/validate.js +39 -0
  272. package/package.json +70 -26
  273. package/SECURITY.md +0 -163
  274. package/dist/ct-wasm.d.ts +0 -1
  275. package/dist/ct-wasm.js +0 -3
  276. package/dist/docs/aead.md +0 -363
  277. package/dist/docs/architecture.md +0 -1011
  278. package/dist/docs/argon2id.md +0 -305
  279. package/dist/docs/chacha20.md +0 -781
  280. package/dist/docs/exports.md +0 -277
  281. package/dist/docs/fortuna.md +0 -530
  282. package/dist/docs/init.md +0 -301
  283. package/dist/docs/loader.md +0 -256
  284. package/dist/docs/serpent.md +0 -617
  285. package/dist/docs/sha2.md +0 -671
  286. package/dist/docs/sha3.md +0 -612
  287. package/dist/docs/types.md +0 -416
  288. package/dist/docs/utils.md +0 -457
  289. package/dist/embedded/kyber.d.ts +0 -1
  290. package/dist/embedded/kyber.js +0 -3
  291. package/dist/kyber/embedded.d.ts +0 -1
  292. package/dist/kyber/indcpa.d.ts +0 -49
  293. package/dist/kyber/index.d.ts +0 -38
  294. package/dist/kyber/kem.d.ts +0 -21
  295. package/dist/kyber/suite.d.ts +0 -12
  296. /package/dist/{ct.wasm → cte.wasm} +0 -0
package/CLAUDE.md CHANGED
@@ -1,484 +1,127 @@
1
- # leviathan-crypto AI Assistant Guide
1
+ # leviathan-crypto: AI Assistant Guide
2
2
 
3
3
  > [!NOTE]
4
- > This file ships with the package to help AI assistants use this library correctly. Full API documentation is in the `docs/` directory alongside this file.
5
-
6
- > ### Table of Contents
7
- > - [What This Library Is](#what-this-library-is)
8
- > - [Critical: stateful classes hold exclusive access](#critical-stateful-classes-hold-exclusive-access)
9
- > - [Critical: `init()` is required](#critical-init-is-required)
10
- > - [Critical: call `dispose()` after use](#critical-call-dispose-after-use)
11
- > - [Critical: `decrypt()` throws on authentication failure](#critical-decrypt-throws-on-authentication-failure--never-returns-null)
12
- > - [Critical: subpath init function names](#critical-subpath-init-function-names)
13
- > - [Which module does each class require?](#which-module-does-each-class-require)
14
- > - [Recommended patterns](#recommended-patterns)
15
- > - [`SerpentCbc` arg order](#serpentcbc-arg-order)
16
- > - [Utilities (no `init()` required)](#utilities-no-init-required)
17
- > - [Full documentation](#full-documentation)
18
-
19
- ---
20
-
21
- ## What This Library Is
22
-
23
- `leviathan-crypto` is a zero-dependency WebAssembly cryptography library for
24
- TypeScript and JavaScript. All cryptographic computation runs in WASM, outside
25
- the JavaScript JIT. The TypeScript layer provides the public API: input
26
- validation, type safety, and ergonomics. It never implements cryptographic
27
- algorithms itself.
28
-
29
- ---
30
-
31
- ## Critical: stateful classes hold exclusive access
32
-
33
- > [!CAUTION]
34
- > Stateful classes (`SHAKE128`, `SHAKE256`, `ChaCha20`, `SerpentCtr`,
35
- > `SerpentCbc`) hold exclusive access to their WASM module for their entire
36
- > lifetime. Construct, use, `dispose()` — in that order. Attempting to
37
- > construct a second stateful instance on the same module throws. Atomic
38
- > one-shot classes (`SHA256`, `SHA3_*`, `HMAC_*`, `Poly1305`, AEAD classes)
39
- > also throw if the module is held by a stateful class. Pool workers are
40
- > unaffected (each worker has its own WASM instance).
41
-
42
- Every TS wrapper in a given WASM module shares one `WebAssembly.Instance` and
43
- therefore one linear memory. A runtime exclusivity guard prevents two stateful
44
- instances from silently clobbering each other's state:
4
+ > Ships with the npm package. Inside the repo? Read `AGENTS.md` instead.
45
5
 
46
- ```typescript
47
- const a = new SHAKE128()
48
- a.absorb(msg1)
49
- const b = new SHAKE128() // throws — a still owns the 'sha3' module
50
- a.squeeze(32)
51
- a.dispose() // release
52
- const c = new SHAKE128() // ok
53
- ```
54
-
55
- The same applies across class boundaries on the same module — e.g. a live
56
- `SerpentCbc` blocks `new SerpentCtr()`, `new Serpent()`, and
57
- `Seal.encrypt(SerpentCipher, ...)` until `dispose()`. Always wrap stateful
58
- use in `try { ... } finally { x.dispose() }`.
6
+ ## What this is
59
7
 
60
- The exclusivity guard also fires on atomic method calls (`SHA256.hash`,
61
- `HMAC_SHA256.hash`, `Poly1305.mac`, `Serpent.encryptBlock`, `XChaCha20Cipher.sealChunk`,
62
- and their peers) when another stateful instance holds the same WASM module.
63
- This protects pre-existing long-lived atomic instances from having their
64
- WASM state silently clobbered by a later-constructed stateful user. If you
65
- hold a `Fortuna` instance backed by `SerpentGenerator` and also want to use `SerpentCtr`/`SerpentCbc`, or one backed by `ChaCha20Generator` while also using `ChaCha20Poly1305`/`XChaCha20Poly1305`, you must `dispose()` one before operating the other. The library will throw a clear error rather than silently corrupting state.
8
+ Zero-dependency WASM crypto for TS/JS. All compute in WASM (outside JS JIT); TS layer = input validation + ergonomics.
66
9
 
67
- ---
10
+ | Family | Primitives |
11
+ |---|---|
12
+ | Symmetric AEAD | Serpent-256, XChaCha20-Poly1305, AES-256-GCM-SIV |
13
+ | Post-quantum sig | ML-DSA, SLH-DSA, PQ-only hybrid composites |
14
+ | Classical sig | Ed25519, ECDSA-P256, classical+PQ hybrid composites |
15
+ | Key agreement | ML-KEM, X25519 |
16
+ | Transparency log | Merkle log (C2SP-conformant) |
17
+ | Forward-secret ratchet | Signal SPQR KDF (rule 8) |
68
18
 
69
- ## Critical: `init()` is required
19
+ ## API shape
70
20
 
71
- **No class works before `init()` is called.** Calling any class before its
72
- module is loaded throws immediately with a clear error. Call `init()` once at
73
- startup, before any cryptographic operations.
74
-
75
- ```typescript
76
- import { init, Serpent } from 'leviathan-crypto'
77
- import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
78
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
21
+ Two hierarchies, one suite extension point. Tier = data shape. Suite = crypto choice.
79
22
 
80
- await init({ serpent: serpentWasm, sha2: sha2Wasm })
81
- ```
23
+ | Tier | AEAD | Signatures |
24
+ |---|---|---|
25
+ | One-shot | Seal | Sign |
26
+ | Streaming | SealStream / OpenStream | SignStream / VerifyStream |
27
+ | Parallel | SealStreamPool (Web Workers) | n/a |
28
+ | Suite arg | CipherSuite | SignatureSuite |
82
29
 
83
- `init()` accepts a `Partial<Record<Module, WasmSource>>`. Each value is a
84
- `WasmSource`: a gzip+base64 string, `URL`, `ArrayBuffer`, `Uint8Array`,
85
- pre-compiled `WebAssembly.Module`, `Response`, or `Promise<Response>`.
30
+ `Seal` blob = single-chunk `SealStream` output (interchangeable). Symmetric: `SerpentCipher`, `XChaCha20Cipher`, `AESGCMSIVCipher`. `MlKemSuite(MlKem*, inner)` wraps any of them for PQ hybrid (same `CipherSuite` interface).
86
31
 
87
- The `/embedded` subpath exports are the simplest WasmSource: they are the
88
- gzip+base64 blobs for each module, bundled with the package.
32
+ **Prefer the high-level surface (Seal / Sign / Fortuna).** Handles KDF, nonce management, auth, key wipes, counter binding. Rejects tampered/reordered/spliced inputs before plaintext release. Low-level primitives (raw `ChaCha20`, `SerpentCbc`, etc.) require reading their wiki page first.
89
33
 
90
- ---
34
+ ## Rules (cross-cutting foot-guns)
91
35
 
92
- ## Critical: call `dispose()` after use
36
+ 1. **`init()` required.** Nothing works before `init()`. Throws on missing module. Idempotent. Use `/embedded` subpath for bundled gzip+base64 blob.
93
37
 
94
- Every class holds WASM memory containing key material. Call `dispose()` when
95
- done; it zeroes that memory. Not calling `dispose()` leaks key material.
38
+ 2. **`dispose()` stateful in `finally`.** Stateful classes hold key material in WASM until `dispose()` zeros it. Wrap in `try { ... } finally { x.dispose() }`. Atomic one-shots (`Seal.encrypt`, `Sign.sign`, hashes, MACs) self-wipe.
96
39
 
97
- ```typescript
98
- const cipher = new XChaCha20Poly1305()
99
- try {
100
- return cipher.encrypt(key, nonce, plaintext)
101
- } finally {
102
- cipher.dispose()
103
- }
104
- ```
40
+ 3. **Stateful = exclusive module access.** A stateful class (`SHAKE128`, `ChaCha20`, `SerpentCtr/Cbc`, `SealStream`, `MlKem*`, etc.) owns its WASM module for its lifetime. Second stateful instance on same module throws. Atomic methods on same module throw while a stateful holder is alive. Pool workers isolated.
105
41
 
106
- ---
42
+ 4. **AEAD `decrypt()` throws on auth failure.** `Seal.decrypt`, AEAD `decrypt()`, `OpenStream.pull` never return null or corrupted plaintext. Wrong key, tampered blob, corrupted bytes all surface as exceptions.
107
43
 
108
- ## Critical: `decrypt()` throws on authentication failure never returns null
44
+ 5. **Raw `verify()` returns bool. `Sign.verify` throws.** Raw primitives (`MlDsa*.verify`, `SlhDsa*.verify`, hybrid `verifyPrehashed`) return `false` on bad sig; only contract violations throw. `Sign.verify` envelope throws on bad sig (parity with `Seal.decrypt`).
109
45
 
110
- All AEAD `decrypt()` methods throw if authentication fails. Do not check for a
111
- null return; catch the exception.
46
+ 6. **Pure-mode and prehash sigs NOT interchangeable.** `dsa.sign` vs `dsa.verifyHash` bind different M' domain bytes (0x00 vs 0x01, FIPS 204 §3.6.4 / FIPS 205 §10.2.2). Sigs don't cross even with identical messages. SignatureSuite enforces at the type level.
112
47
 
113
- ```typescript
114
- try {
115
- const plaintext = seal.decrypt(key, ciphertext)
116
- } catch {
117
- // wrong key or tampered data
118
- }
119
- ```
48
+ 7. **v3 sign envelope: `ctx` required.** `Sign.sign` / `Sign.verify` / `SignStream` / `VerifyStream` all require `ctx`. Pass `new Uint8Array()` for empty. Each suite prepends `ctxDomain` (blocks cross-suite verify). Per-call `ctx` ≤ 255 bytes (FIPS 204 §3.6.1); longer throws `SigningError('sig-ctx-too-long')`. Per-call ceiling = `253 - len(ctxDomain)` (221-234 bytes across catalog).
120
49
 
121
- ---
50
+ 8. **Ratchet = KDF primitives, not a session.** Forward secrecy + post-compromise security primitives only. State machine, message counters, header format, epoch orchestration, transport = app concerns. NOT a drop-in Signal client.
122
51
 
123
- ## Critical: subpath init function names
52
+ ## Subpath imports
124
53
 
125
- Each subpath export has its own module-specific init function, not `init()`.
126
- These are only needed for tree-shakeable imports. The root barrel `init()` is
127
- the normal path.
54
+ Pattern: `leviathan-crypto/<mod>` exports `<mod>Init(source)`; `leviathan-crypto/<mod>/embedded` exports `<mod>Wasm`. Twelve modules: `serpent`, `chacha20`, `aes`, `sha2`, `sha3`, `keccak`, `mlkem`, `mldsa`, `slhdsa`, `blake3`, `curve25519`, `p256`.
128
55
 
129
- Each init function takes a single `WasmSource` argument. Use the module's
130
- `/embedded` subpath to get the bundled blob as a ready-to-use WasmSource.
56
+ Aliases share binary + instance slot:
131
57
 
132
- | Subpath | Init function | Embedded blob |
133
- |---------|---------------|---------------|
134
- | `leviathan-crypto/serpent` | `serpentInit(source)` | `leviathan-crypto/serpent/embedded` → `serpentWasm` |
135
- | `leviathan-crypto/chacha20` | `chacha20Init(source)` | `leviathan-crypto/chacha20/embedded` → `chacha20Wasm` |
136
- | `leviathan-crypto/sha2` | `sha2Init(source)` | `leviathan-crypto/sha2/embedded` → `sha2Wasm` |
137
- | `leviathan-crypto/sha3` | `sha3Init(source)` | `leviathan-crypto/sha3/embedded` → `sha3Wasm` |
138
- | `leviathan-crypto/keccak` | `keccakInit(source)` | `leviathan-crypto/keccak/embedded` → `keccakWasm` |
139
- | `leviathan-crypto/kyber` | `kyberInit(source)` | `leviathan-crypto/kyber/embedded` → `kyberWasm` |
58
+ | Alias | Backed by |
59
+ |---|---|
60
+ | keccak | sha3 |
61
+ | ed25519 | curve25519 |
62
+ | x25519 | curve25519 |
63
+ | ecdsa | p256 |
140
64
 
141
- ```typescript
142
- // Tree-shakeable — loads only serpent WASM
143
- import { serpentInit, Serpent } from 'leviathan-crypto/serpent'
144
- import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
145
- await serpentInit(serpentWasm)
146
- ```
65
+ No `/embedded`: `leviathan-crypto/ratchet`, `leviathan-crypto/stream`, `leviathan-crypto/sign`, `leviathan-crypto/merkle`.
147
66
 
148
- ---
67
+ ## Class → init modules + wiki
149
68
 
150
- ## Which module does each class require?
69
+ | Class | init modules | wiki |
70
+ |---|---|---|
71
+ | Seal / SealStream / OpenStream / SealStreamPool | varies by suite | https://github.com/xero/leviathan-crypto/wiki/aead |
72
+ | SerpentCipher | serpent, sha2 | https://github.com/xero/leviathan-crypto/wiki/serpent |
73
+ | XChaCha20Cipher | chacha20, sha2 | https://github.com/xero/leviathan-crypto/wiki/chacha20 |
74
+ | AESGCMSIVCipher | aes, sha2 | https://github.com/xero/leviathan-crypto/wiki/aes |
75
+ | MlKemSuite(MlKem*, inner) | mlkem, sha3 + inner | https://github.com/xero/leviathan-crypto/wiki/mlkem |
76
+ | Sign / SignStream / VerifyStream | varies by suite | https://github.com/xero/leviathan-crypto/wiki/signing |
77
+ | MlDsa{44,65,87}Suite (pure, prehash) | mldsa, sha3 (+sha2 for SHA-2 prehash) | https://github.com/xero/leviathan-crypto/wiki/mldsa |
78
+ | SlhDsa{128f,192f,256f}Suite | slhdsa, sha3 (+sha2 for SHA-2 prehash) | https://github.com/xero/leviathan-crypto/wiki/slhdsa |
79
+ | Ed25519Suite / Ed25519PreHashSuite | curve25519 (+sha2 for PreHash) | https://github.com/xero/leviathan-crypto/wiki/ed25519 |
80
+ | EcdsaP256Suite (hedged, low-S) | p256, sha2 | https://github.com/xero/leviathan-crypto/wiki/ecdsa-p256 |
81
+ | MlDsa{44,65}Ed25519Suite (0x20, 0x21) | mldsa, sha3, curve25519, sha2 | https://github.com/xero/leviathan-crypto/wiki/signaturesuite |
82
+ | MlDsa{44,65}EcdsaP256Suite (0x22, 0x23) | mldsa, sha3, p256, sha2 | https://github.com/xero/leviathan-crypto/wiki/signaturesuite |
83
+ | MlDsa{44,65,87}SlhDsa{128f,192f,256f}Suite (0x30-0x32) | mldsa, sha3, slhdsa | https://github.com/xero/leviathan-crypto/wiki/signaturesuite |
84
+ | X25519 | curve25519 | https://github.com/xero/leviathan-crypto/wiki/x25519 |
85
+ | MerkleVerifier / MerkleLog | sha2 + suite (+blake3 if `hashing: 'blake3'`) | https://github.com/xero/leviathan-crypto/wiki/merkle |
86
+ | Sparse PQ Ratchet (KDF, rule 8) | sha2, mlkem, sha3 | https://github.com/xero/leviathan-crypto/wiki/ratchet |
87
+ | Fortuna | one cipher + one hash | https://github.com/xero/leviathan-crypto/wiki/fortuna |
88
+ | SHA-2 / HMAC / HKDF | sha2 | https://github.com/xero/leviathan-crypto/wiki/sha2 |
89
+ | SHA-3 / SHAKE | sha3 | https://github.com/xero/leviathan-crypto/wiki/sha3 |
90
+ | CSHAKE / KMAC / KMACXOF | sha3 | https://github.com/xero/leviathan-crypto/wiki/kmac |
91
+ | BLAKE3 family | blake3 | https://github.com/xero/leviathan-crypto/wiki/blake3 |
151
92
 
152
- | Classes | Required modules |
153
- |---------|-----------------|
154
- | `Serpent`, `SerpentCtr`, `SerpentCbc`, `SerpentCipher` | `init({ serpent: serpentWasm, sha2: sha2Wasm })` |
155
- | `SealStream`, `OpenStream`, `SerpentCipher` (when using SerpentCipher) | `init({ serpent: serpentWasm, sha2: sha2Wasm })` |
156
- | `SealStream`, `OpenStream`, `XChaCha20Cipher` (when using XChaCha20Cipher) | `init({ chacha20: chacha20Wasm, sha2: sha2Wasm })` |
157
- | `SealStreamPool` | depends on cipher: same modules as the cipher suite + `sha2` |
158
- | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305` | `init({ chacha20: chacha20Wasm })` |
159
- | `SHA256`, `SHA384`, `SHA512`, `HMAC_SHA256`, `HMAC_SHA384`, `HMAC_SHA512`, `HKDF_SHA256`, `HKDF_SHA512` | `init({ sha2: sha2Wasm })` |
160
- | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256` | `init({ sha3: sha3Wasm })` or `init({ keccak: keccakWasm })` — `'keccak'` is an alias for `'sha3'` |
161
- | `MlKem512`, `MlKem768`, `MlKem1024` | `init({ kyber: kyberWasm, sha3: sha3Wasm })` — both modules required |
162
- | `Fortuna` | `init(...)` with one cipher module (`serpent` or `chacha20`) plus one hash module (`sha2` or `sha3`). All four combinations are valid. |
163
- | `KDFChain`, `ratchetInit`, `ratchetReady`, `SkippedKeyStore` | `init({ sha2: sha2Wasm })` |
164
- | `kemRatchetEncap`, `kemRatchetDecap`, `RatchetKeypair` | `init({ sha2: sha2Wasm, kyber: kyberWasm, sha3: sha3Wasm })` |
93
+ Other refs:
165
94
 
166
- ---
95
+ | Topic | wiki |
96
+ |---|---|
97
+ | init() / WasmSource | https://github.com/xero/leviathan-crypto/wiki/init |
98
+ | Loading strategies | https://github.com/xero/leviathan-crypto/wiki/loader |
99
+ | CDN usage | https://github.com/xero/leviathan-crypto/wiki/cdn |
100
+ | Content-Security-Policy | https://github.com/xero/leviathan-crypto/wiki/csp |
101
+ | Worked examples | https://github.com/xero/leviathan-crypto/wiki/examples |
102
+ | Utilities | https://github.com/xero/leviathan-crypto/wiki/utils |
103
+ | Argon2id integration | https://github.com/xero/leviathan-crypto/wiki/argon2id |
104
+ | CipherSuite interface | https://github.com/xero/leviathan-crypto/wiki/ciphersuite |
105
+ | SignatureSuite catalog | https://github.com/xero/leviathan-crypto/wiki/signaturesuite |
167
106
 
168
- ## Recommended patterns
107
+ ## Canonical example
169
108
 
170
- ### Authenticated encryption (recommended default)
109
+ `Seal` + `SerpentCipher` round-trip:
171
110
 
172
111
  ```typescript
173
- import { init, Seal, SerpentCipher, randomBytes } from 'leviathan-crypto'
112
+ import { init, Seal, SerpentCipher } from 'leviathan-crypto'
174
113
  import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
175
114
  import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
176
115
 
177
116
  await init({ serpent: serpentWasm, sha2: sha2Wasm })
178
117
 
179
- const key = SerpentCipher.keygen()
180
- const blob = Seal.encrypt(SerpentCipher, key, plaintext)
181
- const decrypted = Seal.decrypt(SerpentCipher, key, blob)
182
- ```
183
-
184
- ### Incremental streaming AEAD
185
-
186
- Use when you cannot buffer the full message before encrypting.
187
-
188
- ```typescript
189
- import { init, SealStream, OpenStream, SerpentCipher, randomBytes } from 'leviathan-crypto'
190
- import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
191
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
192
-
193
- await init({ serpent: serpentWasm, sha2: sha2Wasm })
194
-
195
- const key = randomBytes(32)
196
- const sealer = new SealStream(SerpentCipher, key)
197
- const preamble = sealer.preamble // 20 bytes — send first
198
- const ct0 = sealer.push(chunk0)
199
- const ct1 = sealer.push(chunk1)
200
- const ctLast = sealer.finalize(lastChunk)
201
-
202
- const opener = new OpenStream(SerpentCipher, key, preamble)
203
- const pt0 = opener.pull(ct0)
204
- const pt1 = opener.pull(ct1)
205
- const ptLast = opener.finalize(ctLast)
206
- ```
207
-
208
- ### Length-prefixed streaming (for files and buffered transports)
209
-
210
- Pass `{ framed: true }` to `SealStream` for self-delimiting `u32be` length-prefixed
211
- framing. Use when chunks will be concatenated into a flat byte stream. Omit when the
212
- transport frames messages itself (WebSocket, IPC).
213
-
214
- ```typescript
215
- const sealer = new SealStream(SerpentCipher, key, { framed: true })
216
- ```
217
-
218
- ### XChaCha20-Poly1305
219
-
220
- ```typescript
221
- import { init, XChaCha20Poly1305, randomBytes } from 'leviathan-crypto'
222
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
223
-
224
- await init({ chacha20: chacha20Wasm })
225
-
226
- const aead = new XChaCha20Poly1305()
227
- const key = randomBytes(32)
228
- const nonce = randomBytes(24)
229
- const sealed = aead.encrypt(key, nonce, plaintext, aad?) // ciphertext || tag
230
- const plaintext = aead.decrypt(key, nonce, sealed, aad?) // throws on tamper
231
- aead.dispose()
232
- ```
233
-
234
- Note: `encrypt()` returns ciphertext with the 16-byte Poly1305 tag appended.
235
- `decrypt()` expects the same concatenated format, not separate ciphertext and tag.
236
-
237
- > [!CAUTION]
238
- > **Strict single-use on `encrypt()`.** `ChaCha20Poly1305.encrypt()` and
239
- > `XChaCha20Poly1305.encrypt()` are terminal on **any** throw — including
240
- > `RangeError` on key/nonce length. A retry on the same instance always
241
- > raises the single-use guard, never a fresh length error. Always allocate
242
- > a new AEAD per message. This tightens the 2.0-beta semantics where
243
- > length-validation throws were recoverable.
244
- >
245
- > **`SealStream` / `OpenStream` have a `'failed'` terminal state for crypto
246
- > failures.** Crypto-path throws from `push()`, `pull()`, or `finalize()`
247
- > (auth failure, WASM errors, cipher exceptions) wipe derived keys and
248
- > transition the stream to `'failed'`. Subsequent operations and `seek()`
249
- > throw with `'failed'` in the message. `dispose()` on a `'failed'` stream
250
- > is a no-op.
251
- >
252
- > **Argument errors are non-terminal on both `SealStream` and `OpenStream`.**
253
- > `push()` / `finalize()` throwing `RangeError` for a chunk larger than
254
- > `chunkSize` does NOT wipe keys or enter `'failed'`. Symmetrically,
255
- > `pull()` / `finalize()` throwing `RangeError` for a too-short chunk,
256
- > an oversize chunk, or a framed length-prefix mismatch does NOT wipe keys
257
- > or enter `'failed'` either. The stream stays in `'ready'` and accepts a
258
- > corrected retry. Only auth failures from the crypto path transition to
259
- > `'failed'`. Validation errors depend only on attacker-observable input
260
- > lengths, so this distinction creates no cryptographic oracle.
261
- >
262
- > **`SealStreamPool.seal()` is terminal on any throw.** Worker errors,
263
- > auth failures, output-size overflows, or any other rejection kill the
264
- > pool (`pool is dead`, keys wiped). Construct a new pool to continue.
265
- >
266
- > **`OpenStream.seek` is forward-only and fully validates before mutating.**
267
- > Backward seeks (`index < this.counter`) throw a `RangeError` with
268
- > `'forward-only'` in the message. Indices above `Number.MAX_SAFE_INTEGER`
269
- > throw without mutating `counter`, so the stream stays usable. Construct
270
- > a fresh `OpenStream` from the same preamble to restart from the
271
- > beginning.
272
- >
273
- > **Loader accepts any `PromiseLike<WasmSource>`.** `Promise<Response>`,
274
- > `Promise<ArrayBuffer>`, `Promise<Uint8Array>`, and `Promise<string>`
275
- > (gzip+base64 blob) all work — the loader resolves the thenable and
276
- > re-dispatches by the resolved runtime type. Nesting is capped at depth
277
- > 3; deeper chains throw `TypeError: thenable nesting too deep (max 3)`.
278
-
279
- ### Hashing
280
-
281
- ```typescript
282
- import { init, SHA256, HMAC_SHA256 } from 'leviathan-crypto'
283
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
284
-
285
- await init({ sha2: sha2Wasm })
286
-
287
- const hasher = new SHA256()
288
- const digest = hasher.hash(data) // returns Uint8Array
289
- hasher.dispose()
290
-
291
- const mac = new HMAC_SHA256()
292
- const tag = mac.hash(key, data)
293
- mac.dispose()
294
- ```
295
-
296
- ### SHAKE (XOF — variable-length output)
297
-
298
- ```typescript
299
- import { init, SHAKE128 } from 'leviathan-crypto'
300
- import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
301
-
302
- await init({ sha3: sha3Wasm })
303
-
304
- const xof = new SHAKE128()
305
- xof.absorb(data)
306
- const out1 = xof.squeeze(32) // first 32 bytes of output stream
307
- const out2 = xof.squeeze(32) // next 32 bytes — contiguous XOF stream
308
- xof.dispose()
309
- ```
310
-
311
- ### ML-KEM post-quantum key encapsulation
312
-
313
- ```typescript
314
- import { init, MlKem768 } from 'leviathan-crypto'
315
- import { kyberWasm } from 'leviathan-crypto/kyber/embedded'
316
- import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
317
-
318
- await init({ kyber: kyberWasm, sha3: sha3Wasm })
319
-
320
- const kem = new MlKem768()
321
- const { encapsulationKey, decapsulationKey } = kem.keygen()
322
-
323
- // Encapsulation (sender — public encapsulationKey only)
324
- const { ciphertext, sharedSecret: senderSecret } = kem.encapsulate(encapsulationKey)
325
-
326
- // Decapsulation (recipient — private decapsulationKey)
327
- const recipientSecret = kem.decapsulate(decapsulationKey, ciphertext)
328
-
329
- // senderSecret === recipientSecret (32 bytes)
330
- kem.dispose()
331
- ```
332
-
333
- Kyber classes require **both** `kyber` and `sha3` initialized. ML-KEM produces
334
- a 32-byte shared secret suitable for use as a symmetric key.
335
-
336
- - `encapsulate(ek)` and `decapsulate(dk, c)` now throw `RangeError` on FIPS 203
337
- §7.2/§7.3 validation failure. This is a breaking change from 1.x. Callers that
338
- want to probe a key without triggering an exception can still call the public
339
- `checkEncapsulationKey(ek)` / `checkDecapsulationKey(dk)` boolean methods. The
340
- §7.3 throw is a local-integrity check on key material (ML-KEM assumes `dk` is
341
- recipient-controlled local storage) and is distinct from the FO transform's
342
- implicit-rejection path for tampered ciphertext, which returns a pseudorandom
343
- shared secret rather than throwing.
344
-
345
- ### Fortuna CSPRNG
346
-
347
- `Fortuna.create()` requires explicit `generator` and `hash` parameters. There are no defaults.
348
-
349
- ```typescript
350
- import { init, Fortuna } from 'leviathan-crypto'
351
- import { ChaCha20Generator } from 'leviathan-crypto/chacha20'
352
- import { SHA256Hash } from 'leviathan-crypto/sha2'
353
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
354
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
355
-
356
- await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
357
- const fortuna = await Fortuna.create({ generator: ChaCha20Generator, hash: SHA256Hash })
358
- const bytes = fortuna.get(32)
359
- fortuna.stop()
360
- ```
361
-
362
- Substitute `SerpentGenerator` for `ChaCha20Generator`, or `SHA3_256Hash` for `SHA256Hash`, to use other primitive combinations. Match the `init()` modules to whichever pair you pick.
363
-
364
- ### Sparse Post-Quantum Ratchet (KDF layer only)
365
-
366
- ```typescript
367
- import { init, MlKem768, ratchetInit, kemRatchetEncap, kemRatchetDecap, KDFChain } from 'leviathan-crypto'
368
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
369
- import { kyberWasm } from 'leviathan-crypto/kyber/embedded'
370
- import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
371
-
372
- await init({ sha2: sha2Wasm, kyber: kyberWasm, sha3: sha3Wasm })
373
-
374
- const kem = new MlKem768()
375
- const { encapsulationKey: bobEk, decapsulationKey: bobDk } = kem.keygen()
376
-
377
- // Both parties derive initial keys from a shared secret
378
- const alice = ratchetInit(sharedSecret)
379
- const bob = ratchetInit(sharedSecret)
380
-
381
- // Alice performs a KEM ratchet step; kemCt goes in the message header
382
- const aliceEpoch = kemRatchetEncap(kem, alice.nextRootKey, bobEk)
383
-
384
- // Bob decapsulates after receiving kemCt. Pass bobEk as ownEk — both sides
385
- // bind (peerEk, kemCt, context) into HKDF info with u32be length prefixes.
386
- const bobEpoch = kemRatchetDecap(kem, bob.nextRootKey, bobDk, aliceEpoch.kemCt, bobEk)
387
-
388
- // Both construct KDFChains and derive per-message keys
389
- const aliceSend = new KDFChain(aliceEpoch.sendChainKey)
390
- const bobRecv = new KDFChain(bobEpoch.recvChainKey)
391
- const msgKey = aliceSend.step() // alice encrypts; bob decrypts with bobRecv.step()
392
- aliceSend.dispose()
393
- bobRecv.dispose()
394
- kem.dispose()
395
- ```
396
-
397
- Additional ratchet exports:
398
-
399
- - `SkippedKeyStore` — MKSKIPPED cache (DR spec §3.2/§3.5). `resolve(chain, counter)` returns a `ResolveHandle` — call `handle.commit()` on successful decrypt (wipes the key) and `handle.rollback()` on auth failure (returns the key to the store so a later legitimate delivery at the same counter can still decrypt). Double-settle throws; accessing `handle.key` after settling throws. Split budgets: `maxCacheSize` (default 100) bounds memory, `maxSkipPerResolve` (default 50) bounds per-message HKDF work. Legacy `{ ceiling: N }` still accepted — sets both. `advanceToBoundary(chain, pn)` for epoch transitions; `wipeAll()` on teardown. Requires `sha2`. **Breaking change from 1.x and 2.0-beta:** `resolve` used to return a raw key with delete-on-retrieval semantics.
400
- - `RatchetKeypair` — single-use ek/dk wrapper; `new RatchetKeypair(kem)` generates a keypair, `decap(kem, rk, kemCt)` decapsulates exactly once then wipes the dk, `dispose()` is idempotent. Requires `sha2`, `kyber`, `sha3`.
401
- - `RatchetMessageHeader` — interface `{ epoch, counter, pn?, kemCt? }`; `pn` and `kemCt` present only on the first message of a new epoch.
402
- - `KDFChain.stepWithCounter()` — returns `{ key, counter }` atomically; eliminates the separate `.n` read after `step()`.
403
-
404
- Idiomatic `resolve` usage:
405
-
406
- ```typescript
407
- const h = store.resolve(chain, counter)
118
+ const key = SerpentCipher.keygen()
119
+ const blob = Seal.encrypt(SerpentCipher, key, plaintext)
408
120
  try {
409
- const plaintext = Seal.decrypt(cipher, h.key, ciphertext)
410
- h.commit()
411
- return plaintext
412
- } catch (e) {
413
- h.rollback()
414
- throw e
121
+ const pt = Seal.decrypt(SerpentCipher, key, blob)
122
+ } catch {
123
+ // wrong key, tampered blob, or corrupted bytes
415
124
  }
416
125
  ```
417
126
 
418
- See [docs/ratchet.md](./ratchet.md) for the full API reference including all
419
- error conditions, the A2B direction split, bilateral exchange, group usage,
420
- and context-based session separation.
421
-
422
- ---
423
-
424
- ## `SerpentCbc` arg order
425
-
426
- IV is the **second** argument, not the third:
427
-
428
- ```typescript
429
- cipher.encrypt(key, iv, plaintext) // correct
430
- cipher.decrypt(key, iv, ciphertext) // correct
431
- ```
432
-
433
- `SerpentCbc` is unauthenticated. Always pair with `HMAC_SHA256`
434
- (Encrypt-then-MAC) or use `Seal` with `SerpentCipher` instead.
435
-
436
- `SerpentCbc.decrypt()` throws a single generic `RangeError('invalid ciphertext')`
437
- for every failure mode (empty input, non-multiple-of-16 length, any PKCS7 padding
438
- mismatch) and validates the trailing 16 bytes branch-free. This closes the
439
- Vaudenay 2002 padding-oracle surface on `{ dangerUnauthenticated: true }` callers,
440
- but it is **not a substitute for authentication**. Power users must still apply
441
- Encrypt-then-MAC with `HMAC_SHA256` and verify the tag with `constantTimeEqual`
442
- before calling `decrypt()` — the CT-safe padding check only prevents one class
443
- of leakage, not forgery.
444
-
445
- ---
446
-
447
- ## Utilities (no `init()` required)
448
-
449
- ```typescript
450
- import { hexToBytes, bytesToHex, randomBytes, constantTimeEqual, wipe, hasSIMD } from 'leviathan-crypto'
451
-
452
- // available immediately — no await init() needed
453
- const key = randomBytes(32)
454
- const hex = bytesToHex(key)
455
- const back = hexToBytes(hex)
456
- const safe = constantTimeEqual(a, b) // constant-time equality (branch-free SIMD tail, no post-loop conditional on secret bits) — never use ===
457
- wipe(key) // zero a Uint8Array in place
458
- ```
459
-
460
- `hasSIMD()` returns `true` if the runtime supports WebAssembly SIMD.
461
- Serpent, ChaCha20, and Kyber modules all require SIMD; `init()` throws
462
- a clear error on runtimes without support. SIMD has been a baseline
463
- feature of all major browsers and runtimes since 2021. SHA-2 and SHA-3
464
- modules run on any WASM-capable runtime.
465
-
466
- ---
467
-
468
- ## Full documentation
469
-
470
- The complete API reference ships in `docs/` alongside this file:
471
-
472
- | File | Contents |
473
- |------|----------|
474
- | `docs/serpent.md` | `SerpentCipher`, `Serpent`, `SerpentCtr`, `SerpentCbc` |
475
- | `docs/chacha20.md` | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, `XChaCha20Cipher` |
476
- | `docs/sha2.md` | `SHA256`, `SHA384`, `SHA512`, `HMAC_SHA256`, `HMAC_SHA384`, `HMAC_SHA512`, `HKDF_SHA256`, `HKDF_SHA512` |
477
- | `docs/sha3.md` | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256` |
478
- | `docs/aead.md` | `Seal`, `SealStream`, `OpenStream`, `SealStreamPool`, `CipherSuite` |
479
- | `docs/kyber.md` | `MlKem512`, `MlKem768`, `MlKem1024`, `KyberSuite` — ML-KEM (FIPS 203) API reference |
480
- | `docs/fortuna.md` | `Fortuna` CSPRNG |
481
- | `docs/init.md` | `init()` API, loading modes, subpath imports |
482
- | `docs/utils.md` | Encoding helpers, `constantTimeEqual`, `wipe`, `randomBytes` |
483
- | `docs/types.md` | `Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD` interfaces; `CipherSuite`, `DerivedKeys`, `SealStreamOpts`, `PoolOpts`, `WasmSource` |
484
- | `docs/architecture.md` | Module structure, WASM layer, three-tier design |
127
+ Streaming: `Seal` → `SealStream` + `OpenStream`. PQ hybrid: `SerpentCipher` → `MlKemSuite(new MlKem768(), SerpentCipher)`. Same call site, wire format, catch semantics.