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/README.md CHANGED
@@ -1,30 +1,54 @@
1
1
  [![GitHub Release](https://img.shields.io/github/v/release/xero/leviathan-crypto?sort=semver&display_name=tag&style=flat&logo=github&logoColor=989da4&label=latest%20release&labelColor=161925&color=1c7293)](https://github.com/xero/leviathan-crypto/releases/latest) [![npm package minimized gzipped size](https://img.shields.io/bundlejs/size/leviathan-crypto?format=both&style=flat&logo=googlecontaineroptimizedos&logoColor=989da4&label=package%20size&labelColor=161925&color=1c7293)](https://www.npmjs.com/package/leviathan-crypto) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/xero/leviathan-crypto/test-suite.yml?branch=main&style=flat&logo=github&logoColor=989da4&label=test%20suite&labelColor=161925&color=1a936f)](https://github.com/xero/leviathan-crypto/actions/workflows/test-suite.yml) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/xero/leviathan-crypto/wiki.yml?branch=main&style=flat&logo=gitbook&logoColor=989da4&label=wiki%20publish&labelColor=161925&color=1a936f)](https://github.com/xero/leviathan-crypto/wiki)
2
2
 
3
- ![simd webassembly](https://github.com/xero/leviathan-crypto/raw/main/docs/badge-wasm-simd.svg) ![side-effect free](https://github.com/xero/leviathan-crypto/raw/main/docs/badge-side-effect-free.svg) ![tree-shakeable](https://github.com/xero/leviathan-crypto/raw/main/docs/badge-tree-shakable.svg) ![zero dependencies](https://github.com/xero/leviathan-crypto/raw/main/docs/badge-zero-dependancies.svg) [![MIT Licensed](https://github.com/xero/leviathan-crypto/raw/main/docs/badge-mit-license.svg)](https://github.com/xero/leviathan-crypto/blob/main/LICENSE)
3
+ ![simd webassembly](https://github.com/xero/leviathan-crypto/raw/main/docs/badge-wasm-simd.svg) ![side-effect free](https://github.com/xero/leviathan-crypto/raw/main/docs/badge-side-effect-free.svg) ![tree-shakeable](https://github.com/xero/leviathan-crypto/raw/main/docs/badge-tree-shakeable.svg) ![zero dependencies](https://github.com/xero/leviathan-crypto/raw/main/docs/badge-zero-dependencies.svg) [![MIT Licensed](https://github.com/xero/leviathan-crypto/raw/main/docs/badge-mit-license.svg)](https://github.com/xero/leviathan-crypto/blob/main/LICENSE)
4
4
 
5
5
  <img src="https://github.com/xero/leviathan-crypto/raw/main/docs/logo.svg" alt="Leviathan logo" width="400" >
6
6
 
7
7
  # Leviathan Crypto: post-quantum WASM cryptography
8
8
 
9
- **JS is the problem, SIMD WASM is the solution.** JavaScript engines offer no formal constant-time guarantees. JIT compilers optimize based on runtime patterns, which leak secrets through cache access and instruction timing. By contrast, [WebAssembly](https://github.com/xero/leviathan-crypto/wiki/wasm) executes outside the JIT entirely, running compiled bytecode with linear memory you control. No speculative optimization, no value-dependent branches between source and execution.
9
+ **Zero runtime dependencies.** No NPM graph to audit. No supply chain attack surface.
10
10
 
11
- **WebAssembly is the correctness layer.** All algorithm logic lives in WASM. Six AssemblyScript modules ([`serpent`](https://github.com/xero/leviathan-crypto/wiki/asm_serpent), [`chacha20`](https://github.com/xero/leviathan-crypto/wiki/asm_chacha), [`sha2`](https://github.com/xero/leviathan-crypto/wiki/asm_sha2), [`sha3`](https://github.com/xero/leviathan-crypto/wiki/asm_sha3), [`kyber`](https://github.com/xero/leviathan-crypto/wiki/asm_kyber), and [`ct`](https://github.com/xero/leviathan-crypto/wiki/asm_ct)) compile independently to WASM with SIMD where it pays off. Each module is its own instance with its own linear memory. Within a module, stateful primitives share the instance, and a runtime exclusivity model keeps them from interfering with each other.
11
+ **Tree-shakeable.** Import only what you use. Subpath exports let bundlers exclude everything else.
12
12
 
13
- **TypeScript is the ergonomics layer.** The strongly-typed public API covers [`Seal`](https://github.com/xero/leviathan-crypto/wiki/aead#seal), [`SealStream`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstream), [`SealStreamPool`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstreampool), [`Fortuna`](https://github.com/xero/leviathan-crypto/wiki/fortuna), [`HKDF`](https://github.com/xero/leviathan-crypto/wiki/sha2#hkdf_sha256), [`SkippedKeyStore`](https://github.com/xero/leviathan-crypto/wiki/ratchet#skippedkeystore), and others. The design is misuse-resistant by default. Authentication is verify-then-decrypt; key material wipes on dispose; validation runs before any crypto path; one-shot AEADs lock on first call. TypeScript never implements cryptographic algorithms. It orchestrates the WASM layer and enforces best practice through API shape, not convention.
13
+ **Side-effect free.** Nothing runs on import. [`init()`](https://github.com/xero/leviathan-crypto/wiki/init) is explicit and asynchronous.
14
14
 
15
- **[Serpent-256](https://github.com/xero/leviathan-crypto/wiki/serpent_reference): maximum paranoia.** 32 rounds of S-boxes in pure Boolean logic with no table lookups. An ouroboros devouring every bit, in every block, through every round.
15
+ **Cipher Triptych.** Leviathan provides three ciphers. The implementations all use a round structure that operates as a bitsliced Boolean circuit, implemented with register-only logic and no S-box lookup tables. Each compiles to an independent, v128 SIMD-optimized WebAssembly module with isolated linear memory, which prevents cross-module memory access by design. Every operation zeroes key material on exit, including on failure.
16
16
 
17
- **[XChaCha20-Poly1305](https://github.com/xero/leviathan-crypto/wiki/chacha_reference): precise elegance.** 20 rounds of add-rotate-XOR, choreography without S-boxes or cache-timing leakage. A dance closing with Poly1305's unconditional forgery bound.
17
+ **[Serpent-256](https://github.com/xero/leviathan-crypto/wiki/serpent_reference): maximum paranoia.** 32 rounds of eight different 4-bit S-boxes, each bitsliced as a Boolean circuit with no table lookups. An ouroboros devouring every bit, in every block, through every round.
18
18
 
19
- **Two ciphers, one interface.** Both share the [`CipherSuite`](https://github.com/xero/leviathan-crypto/wiki/ciphersuite) shape and slot into [`Seal`](https://github.com/xero/leviathan-crypto/wiki/aead#seal), [`SealStream`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstream), and [`SealStreamPool`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstreampool) interchangeably. Post-quantum extends the same model, [`KyberSuite`](https://github.com/xero/leviathan-crypto/wiki/ciphersuite#kybersuite) wraps [`MlKem512`](https://github.com/xero/leviathan-crypto/wiki/kyber#parameter-sets), [`MlKem768`](https://github.com/xero/leviathan-crypto/wiki/kyber#parameter-sets), or [`MlKem1024`](https://github.com/xero/leviathan-crypto/wiki/kyber#parameter-sets) around either cipher, and the [SPQR ratchet](https://github.com/xero/leviathan-crypto/wiki/ratchet) builds forward-secret sessions on top.
19
+ **[XChaCha20-Poly1305](https://github.com/xero/leviathan-crypto/wiki/chacha_reference): precise elegance.** 20 rounds of add-rotate-XOR alternating column and diagonal quarter-rounds, choreography without S-boxes or cache-timing leakage. A dance closing with Poly1305's unconditional forgery bound.
20
20
 
21
- **Zero dependencies.** No npm graph to audit. No supply chain attack surface.
21
+ **[AES-256-GCM-SIV](https://github.com/xero/leviathan-crypto/wiki/aes_reference): industry standard, sharpened.** 14 rounds bitsliced into Boolean gates with tower-field S-box with no table lookups. A fresh POLYVAL key per nonce leaves GHASH-key recovery with no target.
22
22
 
23
- **Tree-shakeable.** Import only what you use. Subpath exports let bundlers exclude everything else.
23
+ **A uniform API.** The same call shape drives [authenticated encryption](https://github.com/xero/leviathan-crypto/wiki/aead), [signatures and key agreement](https://github.com/xero/leviathan-crypto/wiki/signing), [post-quantum KEM](https://github.com/xero/leviathan-crypto/wiki/mlkem), [hashing](https://github.com/xero/leviathan-crypto/wiki/hashing), [transparency logs](https://github.com/xero/leviathan-crypto/wiki/merkle), [post-quantum ratchet](https://github.com/xero/leviathan-crypto/wiki/ratchet), the [Fortuna CSPRNG](https://github.com/xero/leviathan-crypto/wiki/fortuna), [utilities](https://github.com/xero/leviathan-crypto/wiki/utils), and [types](https://github.com/xero/leviathan-crypto/wiki/types).
24
24
 
25
- **Side-effect free.** Nothing runs on import. [`init()`](https://github.com/xero/leviathan-crypto/wiki/init) is explicit and asynchronous.
25
+ **The correctness contract.** Every cipher, hash, KEM, and signature scheme derives independently from its authoritative spec, never ported from another implementation. Known-answer test vectors come from spec authors, and cross-checks run against multiple independent reference implementations. The test suite covers unit tests at the primitive level plus end-to-end tests across three browser engines (Chromium, Firefox, WebKit) and Node.js. Detailed reference documentation ships at the [project wiki](https://github.com/xero/leviathan-crypto/wiki).
26
+
27
+ ---
28
+
29
+ ## Highlights
30
+
31
+ | **_I want to..._** | |
32
+ |---|---|
33
+ | Encrypt data | [`Seal`](https://github.com/xero/leviathan-crypto/wiki/aead#seal) with [`SerpentCipher`](https://github.com/xero/leviathan-crypto/wiki/serpent#serpentcipher), [`XChaCha20Cipher`](https://github.com/xero/leviathan-crypto/wiki/chacha20#xchacha20cipher), or [`AESGCMSIVCipher`](https://github.com/xero/leviathan-crypto/wiki/aes#aesgcmsivcipher) |
34
+ | Encrypt a stream or large file | [`SealStream`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstream) to encrypt, [`OpenStream`](https://github.com/xero/leviathan-crypto/wiki/aead#openstream) to decrypt |
35
+ | Encrypt in parallel | [`SealStreamPool`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstreampool) distributes chunks across Web Workers |
36
+ | Add post-quantum security | [`MlKemSuite`](https://github.com/xero/leviathan-crypto/wiki/mlkem#mlkemsuite) wraps [`MlKem512`](https://github.com/xero/leviathan-crypto/wiki/mlkem#parameter-sets), [`MlKem768`](https://github.com/xero/leviathan-crypto/wiki/mlkem#parameter-sets), or [`MlKem1024`](https://github.com/xero/leviathan-crypto/wiki/mlkem#parameter-sets) with any cipher suite |
37
+ | Build a forward-secret session | [`ratchetInit`](https://github.com/xero/leviathan-crypto/wiki/ratchet#ratchetinit), [`KDFChain`](https://github.com/xero/leviathan-crypto/wiki/ratchet#kdfchain), [`kemRatchetEncap`](https://github.com/xero/leviathan-crypto/wiki/ratchet#kemratchetencap) / [`kemRatchetDecap`](https://github.com/xero/leviathan-crypto/wiki/ratchet#kemratchetdecap), [`SkippedKeyStore`](https://github.com/xero/leviathan-crypto/wiki/ratchet#skippedkeystore) |
38
+ | Sign data with a classical signature | [`Ed25519Suite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#ed25519-suites) / [`Ed25519PreHashSuite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#ed25519-suites) ([ed25519](https://github.com/xero/leviathan-crypto/wiki/ed25519)) or [`EcdsaP256Suite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#ecdsa-p256-suite) ([ecdsa-p256](https://github.com/xero/leviathan-crypto/wiki/ecdsa-p256)) via [`Sign`](https://github.com/xero/leviathan-crypto/wiki/signing#sign) / [`SignStream`](https://github.com/xero/leviathan-crypto/wiki/signing#signstream) / [`VerifyStream`](https://github.com/xero/leviathan-crypto/wiki/signing#verifystream) |
39
+ | Sign data with a post-quantum signature | `MlDsa44/65/87Suite` (+ `*PreHashSuite`) for lattice ML-DSA ([mldsa](https://github.com/xero/leviathan-crypto/wiki/mldsa)) or `SlhDsa128f/192f/256fSuite` (+ `*PreHashSuite`) for hash-based SLH-DSA ([slhdsa](https://github.com/xero/leviathan-crypto/wiki/slhdsa)). Full catalog in [signaturesuite](https://github.com/xero/leviathan-crypto/wiki/signaturesuite) |
40
+ | Sign data with a classical+PQ hybrid | [`MlDsa44Ed25519Suite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#classicalpq-hybrid-composite-encoding), [`MlDsa65Ed25519Suite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#classicalpq-hybrid-composite-encoding), [`MlDsa44EcdsaP256Suite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#classicalpq-hybrid-composite-encoding), [`MlDsa65EcdsaP256Suite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#classicalpq-hybrid-composite-encoding) for `draft-ietf-lamps-pq-composite-sigs` |
41
+ | Sign data with a PQ-only hybrid | [`MlDsa44SlhDsa128fSuite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#pq-only-hybrid-suites), [`MlDsa65SlhDsa192fSuite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#pq-only-hybrid-suites), [`MlDsa87SlhDsa256fSuite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#pq-only-hybrid-suites) for ML-DSA + SLH-DSA composites at matching NIST categories |
42
+ | Build a transparency log | [`MerkleLog`](https://github.com/xero/leviathan-crypto/wiki/merkle#merklelog) for append plus inclusion / consistency proofs, [`MerkleVerifier`](https://github.com/xero/leviathan-crypto/wiki/merkle#merkleverifier) for clients, [`SignedLog`](https://github.com/xero/leviathan-crypto/wiki/merkle#signedlog) for custom storage backends |
43
+ | Exchange a key with a peer | [`X25519`](https://github.com/xero/leviathan-crypto/wiki/x25519) for Curve25519 Diffie-Hellman |
44
+ | Hash data | [`SHA256`](https://github.com/xero/leviathan-crypto/wiki/sha2#sha256), [`SHA384`](https://github.com/xero/leviathan-crypto/wiki/sha2#sha384), [`SHA512`](https://github.com/xero/leviathan-crypto/wiki/sha2#sha512), [`SHA3_256`](https://github.com/xero/leviathan-crypto/wiki/sha3#sha3_256), [`SHA3_512`](https://github.com/xero/leviathan-crypto/wiki/sha3#sha3_512), [`SHAKE256`](https://github.com/xero/leviathan-crypto/wiki/sha3#shake256) ... |
45
+ | Authenticate a message | [`HMAC_SHA256`](https://github.com/xero/leviathan-crypto/wiki/sha2#hmac_sha256), [`HMAC_SHA384`](https://github.com/xero/leviathan-crypto/wiki/sha2#hmac_sha384), [`HMAC_SHA512`](https://github.com/xero/leviathan-crypto/wiki/sha2#hmac_sha512), or [`KMAC256`](https://github.com/xero/leviathan-crypto/wiki/kmac#kmac256) |
46
+ | Derive keys | [`HKDF_SHA256`](https://github.com/xero/leviathan-crypto/wiki/sha2#hkdf_sha256) or [`HKDF_SHA512`](https://github.com/xero/leviathan-crypto/wiki/sha2#hkdf_sha512) |
47
+ | Generate random bytes | [`Fortuna`](https://github.com/xero/leviathan-crypto/wiki/fortuna#api-reference) for forward-secret generation, [`randomBytes`](https://github.com/xero/leviathan-crypto/wiki/utils#randombytes) for one-off use |
48
+ | Compare secrets safely | [`constantTimeEqual`](https://github.com/xero/leviathan-crypto/wiki/utils#constanttimeequal) uses a WASM SIMD path to prevent timing attacks |
49
+ | Work with bytes | [`hexToBytes`](https://github.com/xero/leviathan-crypto/wiki/utils#hextobytes), [`bytesToHex`](https://github.com/xero/leviathan-crypto/wiki/utils#bytestohex), [`wipe`](https://github.com/xero/leviathan-crypto/wiki/utils#wipe), [`xor`](https://github.com/xero/leviathan-crypto/wiki/utils#xor), [`concat`](https://github.com/xero/leviathan-crypto/wiki/utils#concat) ... |
26
50
 
27
- **Audited primitives.** Every implementation is [verified against its specification](https://github.com/xero/leviathan-crypto/wiki/audits).
51
+ *For raw primitives, low-level cipher access, and ASM internals see the [full API reference](https://github.com/xero/leviathan-crypto/wiki/index).*
28
52
 
29
53
  ---
30
54
 
@@ -36,6 +60,8 @@ The TypeScript layer never implements cryptographic algorithms. It manages the b
36
60
 
37
61
  Higher-level classes like `Seal`, `SealStream`, and `SealStreamPool` are pure TypeScript, but they compose WASM-backed primitives (Serpent-CBC, HMAC-SHA256, ChaCha20-Poly1305, and HKDF-SHA256) rather than implementing new cryptographic logic. TypeScript orchestrates, while WASM computes. Pool workers instantiate their own WASM modules and directly call primitives, bypassing the main-thread module cache.
38
62
 
63
+ See [wasm.md](https://github.com/xero/leviathan-crypto/wiki/wasm) for a fuller primer on WebAssembly in the context of this library.
64
+
39
65
  ---
40
66
 
41
67
  ## Installation
@@ -47,8 +73,15 @@ bun i leviathan-crypto
47
73
  npm install leviathan-crypto
48
74
  ```
49
75
 
76
+ v3 is the current stable line; semver applies. Runs in modern browsers, Node.js 22+, Bun, Deno, and Cloudflare Workers.
77
+
50
78
  > [!IMPORTANT]
51
- > [Serpent](https://github.com/xero/leviathan-crypto/wiki/serpent), [ChaCha20](https://github.com/xero/leviathan-crypto/wiki/chacha20), [ML-KEM](https://github.com/xero/leviathan-crypto/wiki/kyber), and [constantTimeEqual](https://github.com/xero/leviathan-crypto/wiki/utils#constanttimeequal) require WebAssembly SIMD support. This has been a baseline feature of all major browsers and runtimes [since 2021](https://caniuse.com/wasm-simd).
79
+ > [Serpent](https://github.com/xero/leviathan-crypto/wiki/serpent), [ChaCha20](https://github.com/xero/leviathan-crypto/wiki/chacha20), [ML-KEM](https://github.com/xero/leviathan-crypto/wiki/mlkem), [AES](https://github.com/xero/leviathan-crypto/wiki/aes), [ML-DSA](https://github.com/xero/leviathan-crypto/wiki/mldsa), [BLAKE3](https://github.com/xero/leviathan-crypto/wiki/blake3), and [constantTimeEqual](https://github.com/xero/leviathan-crypto/wiki/utils#constanttimeequal) require WebAssembly SIMD support. This has been a baseline feature of all major browsers and runtimes [since 2021](https://caniuse.com/wasm-simd).
80
+
81
+ SIMD throughput on Apple Silicon peaks at ~1.3 GB/s for ChaCha20 and ~40 MB/s for Serpent, single-threaded; 1.2-3.2× over scalar. Full matrix across V8, SpiderMonkey, and JSC in [benchmarks](https://github.com/xero/leviathan-crypto/wiki/benchmarks).
82
+
83
+ > [!NOTE]
84
+ > Found a security issue? Don't open a public issue. See [SECURITY.md](./SECURITY.md#reporting-a-vulnerability) for the disclosure policy.
52
85
 
53
86
  ### Loading
54
87
 
@@ -70,6 +103,21 @@ const compiledModule = await WebAssembly.compileStreaming(fetch('/assets/wasm/se
70
103
  await init({ serpent: compiledModule })
71
104
  ```
72
105
 
106
+ All three patterns also work straight from a CDN with no install or bundler:
107
+
108
+ ```html
109
+ <script type="module">
110
+ import { init, Seal, SerpentCipher } from 'https://unpkg.com/leviathan-crypto/dist/index.js'
111
+ import { serpentWasm } from 'https://unpkg.com/leviathan-crypto/dist/serpent/embedded.js'
112
+ import { sha2Wasm } from 'https://unpkg.com/leviathan-crypto/dist/sha2/embedded.js'
113
+
114
+ await init({ serpent: serpentWasm, sha2: sha2Wasm })
115
+ // ... use as normal
116
+ </script>
117
+ ```
118
+
119
+ See the [CDN reference](https://github.com/xero/leviathan-crypto/wiki/cdn) for unpkg/esm.sh, version pinning, SRI, and import maps.
120
+
73
121
  ### Tree-shaking with subpath imports
74
122
 
75
123
  Each module ships as its own subpath export. Bundlers with tree-shaking support and `"sideEffects": false` drop every module you don't import.
@@ -84,33 +132,66 @@ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
84
132
  await serpentInit(serpentWasm)
85
133
  await sha2Init(sha2Wasm)
86
134
 
87
- // ML-KEM requires kyber + sha3
88
- import { kyberInit } from 'leviathan-crypto/kyber'
89
- import { kyberWasm } from 'leviathan-crypto/kyber/embedded'
135
+ // ML-KEM requires mlkem + sha3
136
+ import { mlkemInit } from 'leviathan-crypto/mlkem'
137
+ import { mlkemWasm } from 'leviathan-crypto/mlkem/embedded'
90
138
  import { sha3Init } from 'leviathan-crypto/sha3'
91
139
  import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
92
140
 
93
- await kyberInit(kyberWasm)
141
+ await mlkemInit(mlkemWasm)
94
142
  await sha3Init(sha3Wasm)
95
143
  ```
96
144
 
97
- | Subpath | Entry point |
98
- | ------------------------------------ | ------------------------------ |
99
- | `leviathan-crypto` | `./dist/index.js` |
100
- | `leviathan-crypto/stream` | `./dist/stream/index.js` |
101
- | `leviathan-crypto/serpent` | `./dist/serpent/index.js` |
102
- | `leviathan-crypto/serpent/embedded` | `./dist/serpent/embedded.js` |
103
- | `leviathan-crypto/chacha20` | `./dist/chacha20/index.js` |
104
- | `leviathan-crypto/chacha20/embedded` | `./dist/chacha20/embedded.js` |
105
- | `leviathan-crypto/sha2` | `./dist/sha2/index.js` |
106
- | `leviathan-crypto/sha2/embedded` | `./dist/sha2/embedded.js` |
107
- | `leviathan-crypto/sha3` | `./dist/sha3/index.js` |
108
- | `leviathan-crypto/sha3/embedded` | `./dist/sha3/embedded.js` |
109
- | `leviathan-crypto/kyber` | `./dist/kyber/index.js` |
110
- | `leviathan-crypto/kyber/embedded` | `./dist/kyber/embedded.js` |
111
- | `leviathan-crypto/ratchet` | `./dist/ratchet/index.js` |
112
-
113
- See the [WASM loading reference](https://github.com/xero/leviathan-crypto/wiki/loader) for details.
145
+ Real bundle sizes (esbuild minified + gzip):
146
+
147
+ | Use case | gzip bundle |
148
+ |---|---:|
149
+ | `Seal` + `XChaCha20Cipher` | ~17 KB |
150
+ | `Seal` + `SerpentCipher` | ~29 KB |
151
+ | Merkle log + ML-DSA-44 cosig | ~29 KB |
152
+ | Full root barrel (every export) | ~53 KB |
153
+
154
+ | Subpath | Module |
155
+ | ------------------------------------ | ------------------------------------------------------- |
156
+ | `leviathan-crypto` | root barrel (all exports) |
157
+ | `leviathan-crypto/stream` | cipher-agnostic seal layer |
158
+ | `leviathan-crypto/serpent` | Serpent-256 |
159
+ | `leviathan-crypto/serpent/embedded` | Serpent-256 WASM blob |
160
+ | `leviathan-crypto/chacha20` | XChaCha20-Poly1305 |
161
+ | `leviathan-crypto/chacha20/embedded` | XChaCha20-Poly1305 WASM blob |
162
+ | `leviathan-crypto/sha2` | SHA-2 family (224 / 256 / 384 / 512, HMAC, HKDF) |
163
+ | `leviathan-crypto/sha2/embedded` | SHA-2 WASM blob |
164
+ | `leviathan-crypto/sha3` | SHA-3 / SHAKE family |
165
+ | `leviathan-crypto/sha3/embedded` | SHA-3 WASM blob |
166
+ | `leviathan-crypto/keccak` | Keccak alias for SHA-3 |
167
+ | `leviathan-crypto/keccak/embedded` | Keccak WASM blob (same bytes as `sha3/embedded`) |
168
+ | `leviathan-crypto/mlkem` | ML-KEM |
169
+ | `leviathan-crypto/mlkem/embedded` | ML-KEM WASM blob |
170
+ | `leviathan-crypto/aes` | AES-256-GCM-SIV |
171
+ | `leviathan-crypto/aes/embedded` | AES WASM blob |
172
+ | `leviathan-crypto/blake3` | BLAKE3 |
173
+ | `leviathan-crypto/blake3/embedded` | BLAKE3 WASM blob |
174
+ | `leviathan-crypto/ecdsa` | ECDSA-P256 |
175
+ | `leviathan-crypto/ecdsa/embedded` | NIST P-256 WASM blob |
176
+ | `leviathan-crypto/ed25519` | Ed25519 (pure and Ed25519ph) |
177
+ | `leviathan-crypto/ed25519/embedded` | Curve25519 WASM blob |
178
+ | `leviathan-crypto/mldsa` | ML-DSA |
179
+ | `leviathan-crypto/mldsa/embedded` | ML-DSA WASM blob |
180
+ | `leviathan-crypto/slhdsa` | SLH-DSA |
181
+ | `leviathan-crypto/slhdsa/embedded` | SLH-DSA WASM blob |
182
+ | `leviathan-crypto/x25519` | X25519 (Curve25519 Diffie-Hellman) |
183
+ | `leviathan-crypto/x25519/embedded` | Curve25519 WASM blob (same bytes as `ed25519/embedded`) |
184
+ | `leviathan-crypto/ratchet` | forward-secret ratchet (SPQR) |
185
+ | `leviathan-crypto/sign` | scheme-agnostic signature layer |
186
+ | `leviathan-crypto/merkle` | Merkle log substrate |
187
+
188
+ Subpaths resolve to `./dist/<mod>/index.js` and `./dist/<mod>/embedded.js`.
189
+
190
+ > [!NOTE]
191
+ > See also:
192
+ > - [`package.json`](https://github.com/xero/leviathan-crypto/blob/main/package.json) for the exact export map
193
+ > - [exports reference](https://github.com/xero/leviathan-crypto/wiki/exports) for what each subpath exports
194
+ > - [WASM loading reference](https://github.com/xero/leviathan-crypto/wiki/loader) for the three loading strategies
114
195
 
115
196
  ---
116
197
 
@@ -189,36 +270,103 @@ const decrypted = await pool.open(encrypted)
189
270
  pool.destroy()
190
271
  ```
191
272
 
192
- **_Want post-quantum security?_** [`KyberSuite`](https://github.com/xero/leviathan-crypto/wiki/kyber#kybersuite) wraps ML-KEM and a cipher suite into a hybrid construction. It plugs directly into [`SealStream`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstream). The sender encrypts with the public encapsulation key and only the recipient's private decapsulation key can open it.
273
+ **_Want post-quantum security?_** [`MlKemSuite`](https://github.com/xero/leviathan-crypto/wiki/mlkem#mlkemsuite) wraps ML-KEM and a cipher suite into a hybrid construction. It plugs directly into [`SealStream`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstream). The sender encrypts with the public encapsulation key and only the recipient's private decapsulation key can open it.
193
274
 
194
275
  ```typescript
195
- import { KyberSuite, MlKem768 } from 'leviathan-crypto/kyber'
196
- import { kyberWasm } from 'leviathan-crypto/kyber/embedded'
276
+ import { MlKemSuite, MlKem768 } from 'leviathan-crypto/mlkem'
277
+ import { mlkemWasm } from 'leviathan-crypto/mlkem/embedded'
197
278
  import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
198
279
 
199
- await init({ kyber: kyberWasm, sha3: sha3Wasm, chacha20: chacha20Wasm, sha2: sha2Wasm })
280
+ await init({ mlkem: mlkemWasm, sha3: sha3Wasm, chacha20: chacha20Wasm, sha2: sha2Wasm })
200
281
 
201
- const suite = KyberSuite(new MlKem768(), XChaCha20Cipher)
282
+ const suite = MlKemSuite(new MlKem768(), XChaCha20Cipher)
202
283
  const { encapsulationKey: ek, decapsulationKey: dk } = suite.keygen()
203
284
 
204
- // sender encrypts with the public key
285
+ // sender: encrypts with the public key
205
286
  const sealer = new SealStream(suite, ek)
206
287
  const preamble = sealer.preamble // 1108 bytes: 20B header + 1088B KEM ciphertext
207
288
  const ct0 = sealer.push(chunk0)
208
289
  const ctLast = sealer.finalize(lastChunk)
209
290
 
210
- // recipient decrypts with the private key
291
+ // recipient: decrypts with the private key
211
292
  const opener = new OpenStream(suite, dk, preamble)
212
293
  const pt0 = opener.pull(ct0)
213
294
  const ptLast = opener.finalize(ctLast)
214
295
  ```
215
296
 
297
+ **_Need post-quantum signatures?_** The [sign module](https://github.com/xero/leviathan-crypto/wiki/signing) wraps ML-DSA (FIPS 204) behind a `SignatureSuite` abstraction. `Sign` covers single-shot attached / detached signatures; `SignStream` and `VerifyStream` handle chunked input via HashML-DSA.
298
+
299
+ ```typescript
300
+ import { init, Sign, SignStream, MlDsa65Suite, MlDsa65PreHashSuite } from 'leviathan-crypto'
301
+ import { mldsaWasm } from 'leviathan-crypto/mldsa/embedded'
302
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
303
+
304
+ await init({ mldsa: mldsaWasm, sha3: sha3Wasm })
305
+
306
+ const { pk, sk } = MlDsa65Suite.keygen()
307
+ const msg = new TextEncoder().encode('hello world')
308
+ const ctx = new TextEncoder().encode('myapp/v1')
309
+
310
+ // single-shot
311
+ const blob = Sign.sign(MlDsa65Suite, sk, msg, ctx)
312
+ const payload = Sign.verify(MlDsa65Suite, pk, blob, ctx)
313
+
314
+ // streamed (over chunked input)
315
+ const signer = new SignStream(MlDsa65PreHashSuite, sk, ctx)
316
+ signer.update(chunk1)
317
+ signer.update(chunk2)
318
+ const sig = signer.finalize()
319
+ // wire output is signer.preamble + chunk1 + chunk2 + sig
320
+ ```
321
+
322
+ Six ML-DSA suites ship: `MlDsa44Suite` / `MlDsa65Suite` / `MlDsa87Suite` for pure ML-DSA, and `MlDsa44PreHashSuite` / `MlDsa65PreHashSuite` / `MlDsa87PreHashSuite` for HashML-DSA. See the [signing reference](https://github.com/xero/leviathan-crypto/wiki/signing) for the wire format and error reference, and the [signaturesuite reference](https://github.com/xero/leviathan-crypto/wiki/signaturesuite) for the full 22-entry catalog.
323
+
324
+ **_Want belt-and-suspenders post-quantum signatures?_** Three PQ-only hybrid suites pair ML-DSA (lattice) with SLH-DSA (hash-based) at each NIST security category. The combined signature is secure as long as either family holds; a future break in one PQ assumption does not transfer to the other. The wire is one combined byte string the receiver verifies through the same `Sign` entry points.
325
+
326
+ ```typescript
327
+ import { init, Sign, MlDsa65SlhDsa192fSuite } from 'leviathan-crypto'
328
+ import { mldsaWasm } from 'leviathan-crypto/mldsa/embedded'
329
+ import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
330
+ import { slhdsaWasm } from 'leviathan-crypto/slhdsa/embedded'
331
+
332
+ await init({ mldsa: mldsaWasm, sha3: sha3Wasm, slhdsa: slhdsaWasm })
333
+
334
+ const { pk, sk } = MlDsa65SlhDsa192fSuite.keygen()
335
+ const msg = new TextEncoder().encode('release manifest v1.2.3')
336
+ const ctx = new TextEncoder().encode('release-signing/v1')
337
+
338
+ const blob = Sign.sign (MlDsa65SlhDsa192fSuite, sk, msg, ctx)
339
+ const payload = Sign.verify(MlDsa65SlhDsa192fSuite, pk, blob, ctx)
340
+ // throws SigningError if either half fails to verify
341
+ ```
342
+
343
+ Three hybrid suites ship at the matching NIST categories: `MlDsa44SlhDsa128fSuite` (category 1), `MlDsa65SlhDsa192fSuite` (category 3), `MlDsa87SlhDsa256fSuite` (category 5). The PQ-only hybrids complement the planned classical+PQ hybrids; the two families defend against different threat models and the [signaturesuite reference](https://github.com/xero/leviathan-crypto/wiki/signaturesuite) covers when to pick which.
344
+
345
+ **_Need classical ECDSA for X.509, JWS, or TLS interop?_** [`EcdsaP256Suite`](https://github.com/xero/leviathan-crypto/wiki/signaturesuite#ecdsa-p256-suite) wraps ECDSA over NIST P-256 (FIPS 186-5 §6) with SHA-256 prehash baked in. Hedged-by-default per `draft-irtf-cfrg-det-sigs-with-noise-05`, low-S enforced on signer and verifier per RFC 6979 §3.5. Wire bytes are 64-byte raw `r || s`; the [`ecdsaSignatureToDer`](https://github.com/xero/leviathan-crypto/wiki/ecdsa-p256#der-utility) / [`ecdsaSignatureFromDer`](https://github.com/xero/leviathan-crypto/wiki/ecdsa-p256#der-utility) helpers convert between raw and the RFC 3279 §2.2.3 DER form for ecosystem interop.
346
+
347
+ ```typescript
348
+ import { init, Sign, EcdsaP256Suite, ecdsaSignatureToDer } from 'leviathan-crypto'
349
+ import { p256Wasm } from 'leviathan-crypto/ecdsa/embedded'
350
+ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
351
+
352
+ await init({ p256: p256Wasm, sha2: sha2Wasm })
353
+
354
+ const { pk, sk } = EcdsaP256Suite.keygen()
355
+ const msg = new TextEncoder().encode('hello world')
356
+ const sig = Sign.signDetached(EcdsaP256Suite, sk, msg, new Uint8Array(0))
357
+ const ok = Sign.verifyDetached(EcdsaP256Suite, pk, msg, sig, new Uint8Array(0))
358
+
359
+ const der = ecdsaSignatureToDer(sig) // X.509 / JWS / TLS interop
360
+ ```
361
+
362
+ ECDSA-P256 is classical (not post-quantum); pair it with an ML-DSA or SLH-DSA suite when the threat model assumes a future CRQC. ECDSA has no native context parameter, so `EcdsaP256Suite` rejects non-empty `user_ctx`; the reserved classical+PQ hybrid suites at `0x22` / `0x23` will provide context-bound classical+PQ signing.
363
+
216
364
  **_Building a secure messenger?_** The [ratchet module](https://github.com/xero/leviathan-crypto/wiki/ratchet) provides Sparse Post-Quantum Ratchet primitives for consumers who need forward secrecy and post-compromise security at the session layer. [`ratchetInit`](https://github.com/xero/leviathan-crypto/wiki/ratchet#ratchetinit) bootstraps the symmetric chains, [`KDFChain`](https://github.com/xero/leviathan-crypto/wiki/ratchet#kdfchain) derives per-message keys, [`kemRatchetEncap`](https://github.com/xero/leviathan-crypto/wiki/ratchet#kemratchetencap) / [`kemRatchetDecap`](https://github.com/xero/leviathan-crypto/wiki/ratchet#kemratchetdecap) perform the ML-KEM ratchet step, and [`SkippedKeyStore`](https://github.com/xero/leviathan-crypto/wiki/ratchet#skippedkeystore) handles out-of-order delivery.
217
365
 
218
366
  ```typescript
219
367
  import { ratchetInit, KDFChain } from 'leviathan-crypto/ratchet'
220
368
 
221
- await init({ sha2: sha2Wasm }) // KDF layer only; add kyber + sha3 for KEM steps
369
+ await init({ sha2: sha2Wasm }) // KDF layer only; add mlkem + sha3 for KEM steps
222
370
 
223
371
  const { nextRootKey, sendChainKey, recvChainKey } = ratchetInit(sharedSecret)
224
372
  const chain = new KDFChain(sendChainKey)
@@ -239,15 +387,15 @@ for full construction details.
239
387
 
240
388
  **`web`** [ [demo](https://leviathan.3xi.club/web) · [source](https://github.com/xero/leviathan-demos/tree/main/web) · [readme](https://github.com/xero/leviathan-demos/blob/main/web/README.md) ]
241
389
 
242
- A self-contained browser encryption tool in a single HTML file. Encrypt text or files with Serpent-256-CBC and Argon2id key derivation, then share the armored output. No server, no install, no network connection after initial load. The code is written to be read. The Encrypt-then-MAC construction, HMAC input, and Argon2id parameters are all intentional examples worth studying.
390
+ A self-contained browser encryption tool in a single HTML file. Encrypt text or files with Serpent-256-CBC and scrypt key derivation, then share the armored output. No server, no install, no network connection after initial load. The code is written to be read. The Encrypt-then-MAC construction, HMAC input, and scrypt parameters are all intentional examples worth studying.
243
391
 
244
- **`chat`** [ [demo](https://leviathan.3xi.club/chat) · [source](https://github.com/xero/leviathan-demos/tree/main/chat) · [readme](https://github.com/xero/leviathan-demos/blob/main/chat/README.md) ]
392
+ **`tamper`** [ [demo](https://leviathan.3xi.club/tamper) · [source](https://github.com/xero/leviathan-demos/tree/main/tamper) · [readme](https://github.com/xero/leviathan-demos/blob/main/tamper/README.md) ]
245
393
 
246
- End-to-end encrypted chat over X25519 key exchange and XChaCha20-Poly1305 message encryption. The relay server is a dumb WebSocket pipe that never sees plaintext. Messages carry sequence numbers so the protocol detects and rejects replayed messages. The demo deconstructs the protocol step by step with visual feedback for injection and replay attacks.
394
+ A crypto attack-resilience demo. It runs a real two-party encrypted channel, then lets you attack it: forge a replay and the sequence check rejects it, tamper with a frame and the Poly1305 tag fails. Key exchange uses X25519 with HKDF-SHA256, message encryption uses XChaCha20-Poly1305, and the relay server is a dumb WebSocket pipe that never sees plaintext. The demo deconstructs the protocol step by step with visual feedback for injection and replay attacks. For a real, production-ready secure messenger built on the same library, see [COVCOM](https://github.com/xero/covcom).
247
395
 
248
396
  **`cli`** [ [npm](https://www.npmjs.com/package/lvthn) · [source](https://github.com/xero/leviathan-demos/tree/main/cli) · [readme](https://github.com/xero/leviathan-demos/blob/main/cli/README.md) ]
249
397
 
250
- Command-line file encryption tool supporting both Serpent-256 and XChaCha20-Poly1305 via `--cipher`. A single keyfile works with both ciphers. The header byte determines decryption automatically. Chunks distribute across a worker pool sized to `hardwareConcurrency`. Each worker owns an isolated WASM instance with no shared memory. The tool can export its own interactive completions for a variety of shells.
398
+ Command-line file encryption tool supporting Serpent-256-CBC+HMAC-SHA256, XChaCha20-Poly1305, and AES-256-GCM-SIV via `--cipher`. A single keyfile works with all three ciphers. The header byte determines decryption automatically. Chunks distribute across a worker pool sized to `hardwareConcurrency`. Each worker owns an isolated WASM instance with no shared memory. The tool can export its own interactive completions for a variety of shells.
251
399
 
252
400
  ```sh
253
401
  bun add -g lvthn
@@ -259,29 +407,13 @@ cat secret.txt | lvthn encrypt -k my.key --armor > secret.enc
259
407
 
260
408
  Post-quantum cryptography demo simulating a complete ML-KEM key encapsulation ceremony between two browser-side clients. A live wire at the top of the page logs every value that crosses the channel; importantly, the shared secret never appears in the wire. After the ceremony completes, both sides independently derive a symmetric key using HKDF-SHA256 and exchange messages encrypted with XChaCha20-Poly1305. Each wire frame is expandable, revealing the raw nonce, ciphertext, Poly1305 tag, and AAD.
261
409
 
262
- **`COVCOM`** [ [demo](https://leviathan.3xi.club/covcom) · [source](https://github.com/xero/covcom/) · [readme](https://github.com/xero/covcom/blob/master/README.md) ]
263
-
264
- A covert communications application for end-to-end encrypted group conversations. Share an invite, talk, exit, and it's gone. Clients available for both the web and cli, along with a containerized dumb server for managing rooms. No secrets or cleartext beyond the handle you chose to join a room with are ever visible to the server. Featuring sparse post-quantum ratcheting, ML-KEM-768, KDFChains, Seal+KyberSuite, and a XChaCha20-Poly1305 core.
410
+ **`jwt`** [ [demo](https://leviathan.3xi.club/jwt) · [source](https://github.com/xero/leviathan-demos/tree/main/jwt) · [readme](https://github.com/xero/leviathan-demos/blob/main/jwt/README.md) ]
265
411
 
266
- ---
267
-
268
- ## Highlights
412
+ Classical and post-quantum JSON Web Token signing demo in a single self-contained HTML file. It signs the same claims across eleven algorithms: EdDSA and ES256, the post-quantum ML-DSA and SLH-DSA families, and the leviathan hybrid composites. Every algorithm runs through one uniform path on the `Sign` suite API, with no per-algorithm branching. The token renders with its three segments color-coded and a live byte readout, so the cost of quantum resistance is visible: the same token grows from about 220 bytes under Ed25519 to past 66 kilobytes under SLH-DSA-SHAKE-256f. Tamper with the payload and verification rejects it, because the signature covers the original bytes.
269
413
 
270
- | **_I want to..._** | |
271
- |---|---|
272
- | Encrypt data | [`Seal`](https://github.com/xero/leviathan-crypto/wiki/aead#seal) with [`SerpentCipher`](https://github.com/xero/leviathan-crypto/wiki/serpent#serpentcipher) or [`XChaCha20Cipher`](https://github.com/xero/leviathan-crypto/wiki/chacha20#xchacha20cipher) |
273
- | Encrypt a stream or large file | [`SealStream`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstream) to encrypt, [`OpenStream`](https://github.com/xero/leviathan-crypto/wiki/aead#openstream) to decrypt |
274
- | Encrypt in parallel | [`SealStreamPool`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstreampool) distributes chunks across Web Workers |
275
- | Add post-quantum security | [`KyberSuite`](https://github.com/xero/leviathan-crypto/wiki/kyber#kybersuite) wraps [`MlKem512`](https://github.com/xero/leviathan-crypto/wiki/kyber#parameter-sets), [`MlKem768`](https://github.com/xero/leviathan-crypto/wiki/kyber#parameter-sets), or [`MlKem1024`](https://github.com/xero/leviathan-crypto/wiki/kyber#parameter-sets) with any cipher suite |
276
- | Build a forward-secret session | [`ratchetInit`](https://github.com/xero/leviathan-crypto/wiki/ratchet#ratchetinit), [`KDFChain`](https://github.com/xero/leviathan-crypto/wiki/ratchet#kdfchain), [`kemRatchetEncap`](https://github.com/xero/leviathan-crypto/wiki/ratchet#kemratchetencap) / [`kemRatchetDecap`](https://github.com/xero/leviathan-crypto/wiki/ratchet#kemratchetdecap), [`SkippedKeyStore`](https://github.com/xero/leviathan-crypto/wiki/ratchet#skippedkeystore) |
277
- | Hash data | [`SHA256`](https://github.com/xero/leviathan-crypto/wiki/sha2#sha256), [`SHA384`](https://github.com/xero/leviathan-crypto/wiki/sha2#sha384), [`SHA512`](https://github.com/xero/leviathan-crypto/wiki/sha2#sha512), [`SHA3_256`](https://github.com/xero/leviathan-crypto/wiki/sha3#sha3_256), [`SHA3_512`](https://github.com/xero/leviathan-crypto/wiki/sha3#sha3_512), [`SHAKE256`](https://github.com/xero/leviathan-crypto/wiki/sha3#shake256) ... |
278
- | Authenticate a message | [`HMAC_SHA256`](https://github.com/xero/leviathan-crypto/wiki/sha2#hmac_sha256), [`HMAC_SHA384`](https://github.com/xero/leviathan-crypto/wiki/sha2#hmac_sha384), or [`HMAC_SHA512`](https://github.com/xero/leviathan-crypto/wiki/sha2#hmac_sha512) |
279
- | Derive keys | [`HKDF_SHA256`](https://github.com/xero/leviathan-crypto/wiki/sha2#hkdf_sha256) or [`HKDF_SHA512`](https://github.com/xero/leviathan-crypto/wiki/sha2#hkdf_sha512) |
280
- | Generate random bytes | [`Fortuna`](https://github.com/xero/leviathan-crypto/wiki/fortuna#api-reference) for forward-secret generation, [`randomBytes`](https://github.com/xero/leviathan-crypto/wiki/utils#randombytes) for one-off use |
281
- | Compare secrets safely | [`constantTimeEqual`](https://github.com/xero/leviathan-crypto/wiki/utils#constanttimeequal) uses a WASM SIMD path to prevent timing attacks |
282
- | Work with bytes | [`hexToBytes`](https://github.com/xero/leviathan-crypto/wiki/utils#hextobytes), [`bytesToHex`](https://github.com/xero/leviathan-crypto/wiki/utils#bytestohex), [`wipe`](https://github.com/xero/leviathan-crypto/wiki/utils#wipe), [`xor`](https://github.com/xero/leviathan-crypto/wiki/utils#xor), [`concat`](https://github.com/xero/leviathan-crypto/wiki/utils#concat) ... |
414
+ **`COVCOM`** [ [demo](https://leviathan.3xi.club/covcom) · [source](https://github.com/xero/covcom/) · [readme](https://github.com/xero/covcom/blob/master/README.md) ]
283
415
 
284
- *For raw primitives, low-level cipher access, and ASM internals see the [full API reference](https://github.com/xero/leviathan-crypto/wiki/index).*
416
+ Covert communications app suite for private group conversations. Invite, talk, close the client, and the chat vanishes. Every message is encrypted with XChaCha20 and signed with Ed25519. A BLAKE3 fingerprint on each key allows peers to verify one another. SPQR's manual and epoch ratchets add forward secrecy, while post-quantum ML-KEM-768 encapsulation keeps recorded communications unreadable and secure against future cryptanalysis.
285
417
 
286
418
  ---
287
419
 
@@ -292,6 +424,7 @@ A covert communications application for end-to-end encrypted group conversations
292
424
  | [Architecture](https://github.com/xero/leviathan-crypto/wiki/architecture) | Repository structure, module relationships, build pipeline, and buffer layouts |
293
425
  | [Test Suite](https://github.com/xero/leviathan-crypto/wiki/test-suite) | How the test suite works, vector corpus, and gate discipline |
294
426
  | [Security Policy](./SECURITY.md) | Security posture and vulnerability disclosure details |
427
+ | [Audits](https://github.com/xero/leviathan-crypto/wiki/audits) | Every primitive has a published audit covering spec conformance, known-answer tests, constant-time discipline, and ACVP validation where applicable. |
295
428
  | [Lexicon](https://github.com/xero/leviathan-crypto/wiki/lexicon) | Glossary of cryptographic terms |
296
429
  | [WASM Primer](https://github.com/xero/leviathan-crypto/wiki/wasm) | WebAssembly primer in the context of this library |
297
430
  | [CDN](https://github.com/xero/leviathan-crypto/wiki/cdn) | Use leviathan-crypto directly from a CDN with no bundler |
@@ -0,0 +1,40 @@
1
+ /**
2
+ * AES-128/192/256 in CBC mode with PKCS7 padding.
3
+ *
4
+ * **WARNING: CBC mode is unauthenticated.** Always authenticate the output
5
+ * with HMAC-SHA256 (Encrypt-then-MAC) or use an authenticated cipher
6
+ * (`XChaCha20Poly1305`, `Seal` with `SerpentCipher`) instead.
7
+ *
8
+ * Holds exclusive access to the `aes` WASM module from construction until
9
+ * `dispose()`. Constructing a second AES-using class while this instance
10
+ * is live throws. Call `dispose()` when done.
11
+ */
12
+ export declare class AESCbc {
13
+ private readonly x;
14
+ private _tok;
15
+ constructor(opts?: {
16
+ dangerUnauthenticated: true;
17
+ });
18
+ /**
19
+ * Encrypt plaintext with AES CBC + PKCS7 padding.
20
+ *
21
+ * @param key 16, 24, or 32 bytes (AES-128 / 192 / 256)
22
+ * @param iv 16 bytes, must be random and unique per (key, message)
23
+ * @param plaintext any length, PKCS7 padding applied automatically
24
+ * @returns ciphertext (length = ceil((plaintext.length + 1) / 16) * 16)
25
+ */
26
+ encrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Uint8Array;
27
+ /**
28
+ * Decrypt AES CBC + PKCS7.
29
+ *
30
+ * All failure modes, empty input, non-multiple-of-16 length, and any
31
+ * PKCS7 validation failure, throw the same generic `RangeError` with
32
+ * message `'invalid ciphertext'`. Padding validation runs branch-free
33
+ * over the last 16 bytes regardless of where the mismatch is, closing
34
+ * the Vaudenay 2002 padding-oracle surface for callers using
35
+ * `{ dangerUnauthenticated: true }` without an outer HMAC.
36
+ */
37
+ decrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Uint8Array;
38
+ /** Wipe WASM state and release exclusive module access. Idempotent. */
39
+ dispose(): void;
40
+ }
@@ -0,0 +1,158 @@
1
+ // ▄▄▄▄▄▄▄▄▄▄
2
+ // ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
3
+ // ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
4
+ // ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
5
+ // ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
6
+ // ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
7
+ // ███████▌ ▀██▀ ███
8
+ // ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
9
+ // ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
10
+ // ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
11
+ // ▀████▄ ▄██▄
12
+ // ▐████ ▐███ Author: xero (https://x-e.ro)
13
+ // ▄▄██████████ ▐███ ▄▄ License: MIT
14
+ // ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
15
+ // ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
16
+ // ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
17
+ // ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
18
+ // █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
19
+ // ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
20
+ // ▀█████▀▀
21
+ //
22
+ // src/ts/aes/aes-cbc.ts
23
+ //
24
+ // AESCbc, AES-128/192/256 CBC + PKCS7, stateful TS wrapper.
25
+ // SP 800-38A §6.2 (mode), RFC 5652 §6.3 (PKCS7 padding).
26
+ // Mirrors `src/ts/serpent/serpent-cbc.ts` exactly in shape; only the
27
+ // underlying WASM module and the PKCS7 import path differ.
28
+ import { getInstance, _acquireModule, _releaseModule } from '../init.js';
29
+ import { pkcs7Pad, pkcs7Strip, PKCS7_INVALID } from '../shared/pkcs7.js';
30
+ /** Returns the raw AES WASM export object. @internal */
31
+ function getExports() {
32
+ return getInstance('aes').exports;
33
+ }
34
+ // ── AESCbc ──────────────────────────────────────────────────────────────────
35
+ /**
36
+ * AES-128/192/256 in CBC mode with PKCS7 padding.
37
+ *
38
+ * **WARNING: CBC mode is unauthenticated.** Always authenticate the output
39
+ * with HMAC-SHA256 (Encrypt-then-MAC) or use an authenticated cipher
40
+ * (`XChaCha20Poly1305`, `Seal` with `SerpentCipher`) instead.
41
+ *
42
+ * Holds exclusive access to the `aes` WASM module from construction until
43
+ * `dispose()`. Constructing a second AES-using class while this instance
44
+ * is live throws. Call `dispose()` when done.
45
+ */
46
+ export class AESCbc {
47
+ x;
48
+ _tok;
49
+ constructor(opts) {
50
+ if (!opts?.dangerUnauthenticated) {
51
+ throw new Error('leviathan-crypto: AESCbc is unauthenticated, use Seal with SerpentCipher or XChaCha20Cipher instead. ' +
52
+ 'To use AESCbc directly, pass { dangerUnauthenticated: true }.');
53
+ }
54
+ this.x = getExports();
55
+ this._tok = _acquireModule('aes');
56
+ }
57
+ /** View over WASM linear memory. Rebind on every access, memory can be detached after grow. @internal */
58
+ get mem() {
59
+ return new Uint8Array(this.x.memory.buffer);
60
+ }
61
+ /**
62
+ * Encrypt plaintext with AES CBC + PKCS7 padding.
63
+ *
64
+ * @param key 16, 24, or 32 bytes (AES-128 / 192 / 256)
65
+ * @param iv 16 bytes, must be random and unique per (key, message)
66
+ * @param plaintext any length, PKCS7 padding applied automatically
67
+ * @returns ciphertext (length = ceil((plaintext.length + 1) / 16) * 16)
68
+ */
69
+ encrypt(key, iv, plaintext) {
70
+ if (this._tok === undefined)
71
+ throw new Error('AESCbc: instance has been disposed');
72
+ this._loadKey(key);
73
+ this._setIv(iv);
74
+ const padded = pkcs7Pad(plaintext);
75
+ const output = new Uint8Array(padded.length);
76
+ const ptOff = this.x.getChunkPtOffset();
77
+ const ctOff = this.x.getChunkCtOffset();
78
+ const maxChunk = this.x.getChunkSize();
79
+ for (let off = 0; off < padded.length; off += maxChunk) {
80
+ const chunk = padded.subarray(off, Math.min(off + maxChunk, padded.length));
81
+ this.mem.set(chunk, ptOff);
82
+ const ret = this.x.cbcEncryptChunk(chunk.length);
83
+ if (ret < 0)
84
+ throw new RangeError(`cbcEncryptChunk rejected len=${chunk.length}` +
85
+ ` (WASM CHUNK_SIZE=${this.x.getChunkSize()})`);
86
+ output.set(new Uint8Array(this.x.memory.buffer).subarray(ctOff, ctOff + chunk.length), off);
87
+ }
88
+ return output;
89
+ }
90
+ /**
91
+ * Decrypt AES CBC + PKCS7.
92
+ *
93
+ * All failure modes, empty input, non-multiple-of-16 length, and any
94
+ * PKCS7 validation failure, throw the same generic `RangeError` with
95
+ * message `'invalid ciphertext'`. Padding validation runs branch-free
96
+ * over the last 16 bytes regardless of where the mismatch is, closing
97
+ * the Vaudenay 2002 padding-oracle surface for callers using
98
+ * `{ dangerUnauthenticated: true }` without an outer HMAC.
99
+ */
100
+ decrypt(key, iv, ciphertext) {
101
+ if (this._tok === undefined)
102
+ throw new Error('AESCbc: instance has been disposed');
103
+ if (ciphertext.length === 0 || ciphertext.length % 16 !== 0)
104
+ throw new RangeError(PKCS7_INVALID);
105
+ this._loadKey(key);
106
+ this._setIv(iv);
107
+ const output = new Uint8Array(ciphertext.length);
108
+ const ctOff = this.x.getChunkCtOffset();
109
+ const ptOff = this.x.getChunkPtOffset();
110
+ const maxChunk = this.x.getChunkSize();
111
+ for (let off = 0; off < ciphertext.length; off += maxChunk) {
112
+ const chunk = ciphertext.subarray(off, Math.min(off + maxChunk, ciphertext.length));
113
+ this.mem.set(chunk, ctOff);
114
+ const ret = this.x.cbcDecryptChunk_simd(chunk.length);
115
+ if (ret < 0)
116
+ throw new RangeError(`cbcDecryptChunk_simd rejected len=${chunk.length}` +
117
+ ` (WASM CHUNK_SIZE=${this.x.getChunkSize()})`);
118
+ output.set(new Uint8Array(this.x.memory.buffer).subarray(ptOff, ptOff + chunk.length), off);
119
+ }
120
+ return pkcs7Strip(output);
121
+ }
122
+ /** Wipe WASM state and release exclusive module access. Idempotent. */
123
+ dispose() {
124
+ if (this._tok === undefined)
125
+ return;
126
+ try {
127
+ this.x.wipeBuffers();
128
+ }
129
+ finally {
130
+ _releaseModule('aes', this._tok);
131
+ this._tok = undefined;
132
+ }
133
+ }
134
+ /**
135
+ * Validate and load `key` into the WASM key schedule.
136
+ * @param key 16, 24, or 32 bytes
137
+ * @internal
138
+ */
139
+ _loadKey(key) {
140
+ if (key.length !== 16 && key.length !== 24 && key.length !== 32)
141
+ throw new RangeError(`AES key must be 16, 24, or 32 bytes (got ${key.length})`);
142
+ this.mem.set(key, this.x.getKeyOffset());
143
+ if (this.x.loadKey(key.length) !== 0) {
144
+ this.x.wipeBuffers();
145
+ throw new Error('AESCbc: loadKey failed');
146
+ }
147
+ }
148
+ /**
149
+ * Write `iv` into the WASM CBC IV buffer.
150
+ * @param iv 16 bytes
151
+ * @internal
152
+ */
153
+ _setIv(iv) {
154
+ if (iv.length !== 16)
155
+ throw new RangeError(`CBC IV must be 16 bytes (got ${iv.length})`);
156
+ this.mem.set(iv, this.x.getCbcIvOffset());
157
+ }
158
+ }